Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 51 additions & 10 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Thank you for your interest in contributing! This guide covers everything you ne
- [Pull Request Process](#pull-request-process)
- [Running Tests](#running-tests)
- [Code Style](#code-style)
- [Frontend Styling (CSP-Safe)](#frontend-styling-csp-safe)
- [Security](#security)

---
Expand Down Expand Up @@ -307,20 +308,60 @@ that at minimum covers:
## Minimum Supported Rust Version (MSRV)

The `services/api` crate declares a `rust-version` field in its `Cargo.toml`.
This is the **oldest** Rust toolchain version the c
This is the **oldest** Rust

---

## Security
## Frontend Styling (CSP-Safe)

### The rule: no inline `style` props

Do **not** use the `style={{ ... }}` prop on React/Next.js components for
anything that affects rendering. Use CSS classes (or CSS modules) instead.

```tsx
// ❌ Don't — CSP strips this at runtime, so the style silently disappears
<div style={{ padding: 16, color: 'red' }}>…</div>

// ✅ Do — put the rules in a stylesheet and reference them by class
<div className="panel panel--error">…</div>
```

### Why (this is not a style preference)

Please **do not** report security vulnerabilities through public GitHub issues,
discussions, or pull requests.
The frontend is served under a strict **Content-Security-Policy** that does not
allow inline styles. When a component sets `style={{ ... }}`, the browser
**blocks or strips** the resulting inline style attribute — the element renders
with no styling at all. This is a real production bug that was fixed in
`5bd5e51` (AppShell chrome) and `e80a15b` (all remaining inline `style` props),
and it is easy to reintroduce by accident because the code still *looks*
correct in the editor and in tests that don't enforce CSP.

Instead, report them privately via **GitHub Security Advisories** for this
repository:
Because the failure is silent (no error, just missing styles), the convention
below is mandatory rather than optional.

- https://github.com/solutions-plug/predictIQ/security/advisories/new
### Required pattern

- Define styles in CSS (global stylesheet or a CSS module) and apply them via
`className`.
- For dynamic values, prefer a class variant (e.g. `panel--error`) or a CSS
custom property set through a class, rather than a `style` prop.
- If a value genuinely cannot be expressed as a class, raise it in the PR
description so the CSP implications can be reviewed — do not add an inline
`style` prop silently.

### Enforcement

There is currently **no lint rule or CI check** that blocks inline `style`
props, so this convention relies on review. Adding an ESLint rule such as
[`react/forbid-dom-props`](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/forbid-dom-props.md)
configured to forbid `style` is recommended and tracked separately; until it
lands, reviewers should flag any new `style={{ ... }}` prop.

---

## Security

This is the verified, monitored channel for security reports. See
[`SECURITY.md`](SECURITY.md) for the full disclosure policy and response
timelines.
If you discover a security vulnerability, please **do not** open a public issue.
Instead, report it privately to the maintainers so it can be triaged and fixed
before disclosure.
52 changes: 52 additions & 0 deletions frontend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Frontend

This package contains the Handsoff web frontend.

## Styling conventions (CSP-safe, no inline styles)

### Why: the CSP constraint

Our production Content-Security-Policy does **not** allow inline styles. As a
result, any `style={{ ... }}` prop passed to a component is silently dropped by
the browser — the element renders with no styling at all, and the bug is easy to
miss in development where the policy may be relaxed.

This was a real production incident: commits `5bd5e51` ("move AppShell chrome
off inline styles, CSP was dropping them") and `e80a15b` ("migrate every
remaining inline style prop to CSS classes") fixed it by removing inline styles
entirely. The constraint is documented here so the same bug is not reintroduced.

### The rule

**Do not use the `style` prop for anything that affects rendering.**

```jsx
// ❌ Wrong — stripped by CSP, element renders unstyled
<div style={{ marginTop: 8, color: 'red' }}>…</div>

// ✅ Correct — use a CSS class
<div className="stack-sm text-danger">…</div>
```

Use CSS classes (or CSS modules) for all styling. Dynamic values that would
normally be computed inline should be expressed as a class variant, a CSS custom
property set through a class, or a data attribute selected in CSS — never as an
inline `style` prop.

### Enforcement

There is currently **no lint rule or CI check** enforcing this, so it relies on
review. We should add one: an ESLint rule such as
[`react/forbid-dom-props`](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/forbid-dom-props.md)
configured to forbid the `style` prop would catch regressions at lint time:

```json
{
"rules": {
"react/forbid-dom-props": ["error", { "forbid": ["style"] }]
}
}
```

Until that rule (or an equivalent CI check) lands, treat any new `style` prop as
a review blocker.
60 changes: 60 additions & 0 deletions frontend/docs/styling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Frontend Styling Conventions

## The rule: no inline `style` props

Do **not** use the `style` prop on React components or DOM elements for anything
that affects rendering:

```jsx
// ❌ Don't do this
<div style={{ marginTop: 8, color: 'red' }}>…</div>
```

Instead, express styling through CSS classes (or CSS modules) and apply them via
`className`:

```jsx
// ✅ Do this
<div className="stack-sm text-danger">…</div>
```

## Why: Content Security Policy strips inline styles

Our production Content Security Policy does not allow inline styles. When the
browser enforces that policy, inline `style` attributes are **silently dropped** —
the element still renders, but without the styling. This is not a build error and
not a test failure; it only shows up in production, which is exactly why it went
unnoticed for so long.

This was a real production bug. Two commits fixed it:

- `5bd5e51` — moved AppShell chrome off inline styles (CSP was dropping them)
- `e80a15b` — migrated every remaining inline `style` prop to CSS classes

Because the failure mode is silent and environment-specific, the constraint is
easy to reintroduce by accident. That is why it is documented here: so a future
PR does not ship the same bug again.

## The required pattern

- Put styling in CSS (global stylesheets or CSS modules) and reference it with
`className`.
- For dynamic values that genuinely must vary at runtime, prefer toggling a
class (e.g. `className={isActive ? 'is-active' : ''}`) or setting a CSS custom
property through a class rather than writing an inline `style` object.
- If you believe you have a case that truly requires an inline style, raise it in
the PR description first — do not add one silently.

## Enforcement

There is currently **no** lint rule or CI check that blocks inline `style` props,
so nothing mechanically prevents a regression. Adding one is recommended:

- ESLint's [`react/forbid-dom-props`](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/forbid-dom-props.md)
can be configured with `forbid: ['style']` to flag inline `style` props on DOM
elements.
- For components that forward a `style` prop, `react/forbid-component-props`
covers the same case.

Until such a rule is enabled, this convention is enforced by review. Please keep
it in mind when reviewing frontend changes.
Loading