You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit a6a7d80
Browse filesBrowse the repository at this point in the historyBrowse files
Copy file name to clipboardExpand all lines: .claude/rules/sim-platform-views.md
+13-6Lines changed: 13 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -20,21 +20,26 @@ like that" is not one.
20
20
| Job | Use | Never |
21
21
| --- | --- | --- |
22
22
| Explain a field | Its label, placeholder and options (`Access keys (long-lived)`, `Description (optional)`); `error` for validation, `disabledReason` for a locked control, the dialog's Setup guide for steps | A field `hint` or data `hint` (`check:platform-design``helper-copy`; a line that must stay is named in `HELPER_COPY_OWNERS`) |
23
-
| Explain a dialog, settings page or row | A one-sentence form `Modal``description`; the page's `description` in `apps/sim/platform/lib/data/settings-navigation.ts`; a concise `description` on every `SettingsRow`, `BooleanRow` and `DetailHeader` (`Select your preferred color theme`) | An About tooltip on a settings row, or a hand-written subtitle `<p>`|
23
+
| Explain a dialog, settings page or row | A one-sentence form `Modal``description`; the page's `description` in `apps/sim/platform/lib/data/settings-navigation.ts` (a workspace's in `apps/sim/platform/lib/data/workspace-settings/navigation.ts`); a concise `description` on every `SettingsRow`, `BooleanRow` and `DetailHeader` (`Select your preferred color theme`) | An About tooltip on a settings row, or a hand-written subtitle `<p>`|
24
24
| Settings row choice | A bordered `Select` (`SearchableSelect` for a long list) in `SettingsRow field`| A `ChoiceMenu` or `variant='text'` trigger for a saved preference |
25
25
| First read of a view | Render nothing for data-backed content while pending (static chrome renders at once), `FailureNotice` when the read fails, `EmptyState` when it settles empty. Read the query's `status`, not an empty list. An action's own pending label (`pendingLabel`, a Load action's `Loading…`) is feedback, not loading copy | A skeleton, shimmer, placeholder rows or loading copy (`check:platform-design``loading-copy`); a pending hint, placeholder or detail line; treating empty as loading |
26
26
| Failed read |`FailureNotice`, labelled Retry. `intent='warning'` where the read partly answered | A hand-built retry button or `InlineNotice` + `Button`|
27
27
| Failed background refresh |`FailureNotice` above the content it keeps | Replacing content already on screen |
28
28
| Empty |`EmptyState`, with no create button when the page already has one. A picker's no-match line inside a dialog stays `Muted`, since a block would take over the dialog |`Muted` or a raw `<p>` anywhere else |
29
29
| Confirm |`ConfirmDialog` (`target`, `title`, `confirmLabel`, `pendingLabel`, `onConfirm`, `safe` when nothing is lost). Removing a person, revoking an invite and any destructive row action always ask first |`Modal variant='confirm'`, `window.confirm`, an unconfirmed destructive action |
30
-
| Save |`SaveActions`, end-aligned in the page flow under the fields it commits: Discard while dirty, then Save. Save is visible and disabled while clean| Save in a header, Save before Discard, a Cancel beside Discard |
30
+
| Save |`SettingsSaveBar` (`SaveActions` plus the leave guard), end-aligned in the page flow under the fields it commits: Discard while dirty, then Save. Save is visible and disabled while clean. A create form keeps its own Create {noun} or Add {noun} submit |`SaveActions` or a Discard button outside the bar (`save-owner`), Save in a header, Save before Discard, a Cancel beside Discard |
31
31
| Autosave vs Save | One or the other per page. A page with `SaveActions` holds every field in one draft and writes it in one request. List actions with their own dialog, such as adding or removing a member, are not field edits | A page mixing both |
32
32
| Create | A primary compact button in `SectionToolbar`, or in the page header on a page with one | Create in both the toolbar and the empty state |
33
33
| Search over a section's rows |`SearchField` in the toolbar's `grow` slot, labelled and placeholdered `Search <subject>…`| A hand-built input, or copy that names more than the rows it filters |
34
34
| Row |`ActivityRow` for a row that opens something, `SettingsRow` for a row holding a control | A hand-built row |
35
-
| List of rows |`RowList` (`dividers` for a list read as one block). `ActivityRow density='comfortable'` in settings |`calc(-1 * var(--ui-row-inset))` around a list; styling a row's height from outside |
35
+
| List of rows |`ResourceList`, which bleeds through `PageOverflow`. `ActivityRow density='comfortable'` in settings |`calc(-1 * var(--ui-row-inset))` around a list; styling a row's height from outside |
36
+
| Empty settings list |`SettingsList` with `empty`: the `EmptyState` in place of the surface, under the section title | An `EmptyState` inside a `SettingsSurface` or `SettingsSection` (`empty-in-surface`) |
37
+
| On-or-off setting |`SwitchRow`| A `Switch` in a `SettingsRow` (`switch-row`) |
36
38
| Row actions |`ActionMenu` with icon `I108`, destructive item last with a separator | The same action in the row and its menu |
37
-
| Section heading |`SettingsSection title` (h2), `DetailHeader` for a sub-view's way back | A hand-written heading |
39
+
| Page heading |`SettingsPageHeader` in a section's main view, `DetailHeader` in a sub-view: exactly one per page, in every state | A heading in the frame, a sub-view's title under the page's own |
| Open a sub-view |`useSettingsSubview`: the record's id, or the create flow, in the URL | A detail or create flow in `useState`, which Back and refresh lose |
42
+
| One read |`SettingsRead` with the query and its failure message | A hand-built pending, failure and refresh-failure branch |
38
43
| Toolbar |`SectionToolbar grow={<SearchField label='Search members' … />}` with its actions as children | A hand-built flex row |
39
44
40
45
## Failure feedback
@@ -60,5 +65,7 @@ user activation is spent, `cancel` cannot be prevented, so `OverlayDialog` reope
60
65
61
66
- A section with more than one job splits by job into sibling files, as `settings/_components/sections/access-control/`
62
67
does (`group-detail.tsx`, `permission-rows.tsx`, `add-members-modal.tsx`).
63
-
- What organization and workspace Settings share is in `_components/settings-parts/`
64
-
(`apps/sim/platform/docs/component-ownership.md`); a view's data and logic follow `AGENTS.md` "Data seam".
68
+
- What organization and workspace Settings share is in `_components/settings-parts/`: `SettingsBody` and
69
+
`SettingsPageHeader` (`settings-page.tsx`), the parts in `settings-parts.tsx`, and saved preferences in
Open the panel/modal only when the id **resolves to a loaded entity** — never gate on the raw param alone, or a dead/stale id (deleted entity, old bookmark) renders a broken detail view and a still-loading list flashes one. A dead id simply falls back to the list; the lingering param is harmless. Because this reads `useSearchParams` it needs a **Suspense** boundary on the page (see "Suspense boundary" above). A separate "create new" flow has no id and stays in local `useState`.
253
+
Open the panel/modal only when the id **resolves to a loaded entity** — never gate on the raw param alone, or a dead/stale id (deleted entity, old bookmark) renders a broken detail view and a still-loading list flashes one. A dead id simply falls back to the list; the lingering param is harmless. Because this reads `useSearchParams` it needs a **Suspense** boundary on the page (see "Suspense boundary" above). A separate "create new" flow has no id and stays in local `useState`, except in Platform Settings, where a section opens its record and create sub-views through `useSettingsSubview` (`apps/sim/platform/lib/data/settings-subview.ts`), which keeps both in the URL with this push/replace convention.
254
254
255
255
**Close with `replace`, open with `push`.** Opening pushed a history entry; closing must not push another. Close via the setter's per-call options — `setSkillId(null, { history: 'replace' })` — so Back from the list leaves the page instead of reopening the detail (see `mcp-section.tsx`, `custom-blocks-section.tsx`, `forks-section.tsx`). Secondary params scoped to the detail view (e.g. its active tab, `server-tab`) are cleared in the same close handler with their own setter — nuqs batches same-tick writes into one URL update.
Copy file name to clipboardExpand all lines: .cursor/rules/sim-platform-views.mdc
+13-6Lines changed: 13 additions & 6 deletions
Original file line number
Diff line number
Diff line change
@@ -18,21 +18,26 @@ like that" is not one.
18
18
| Job | Use | Never |
19
19
| --- | --- | --- |
20
20
| Explain a field | Its label, placeholder and options (`Access keys (long-lived)`, `Description (optional)`); `error` for validation, `disabledReason` for a locked control, the dialog's Setup guide for steps | A field `hint` or data `hint` (`check:platform-design` `helper-copy`; a line that must stay is named in `HELPER_COPY_OWNERS`) |
21
-
| Explain a dialog, settings page or row | A one-sentence form `Modal` `description`; the page's `description` in `apps/sim/platform/lib/data/settings-navigation.ts`; a concise `description` on every `SettingsRow`, `BooleanRow` and `DetailHeader` (`Select your preferred color theme`) | An About tooltip on a settings row, or a hand-written subtitle `<p>` |
21
+
| Explain a dialog, settings page or row | A one-sentence form `Modal` `description`; the page's `description` in `apps/sim/platform/lib/data/settings-navigation.ts` (a workspace's in `apps/sim/platform/lib/data/workspace-settings/navigation.ts`); a concise `description` on every `SettingsRow`, `BooleanRow` and `DetailHeader` (`Select your preferred color theme`) | An About tooltip on a settings row, or a hand-written subtitle `<p>` |
22
22
| Settings row choice | A bordered `Select` (`SearchableSelect` for a long list) in `SettingsRow field` | A `ChoiceMenu` or `variant='text'` trigger for a saved preference |
23
23
| First read of a view | Render nothing for data-backed content while pending (static chrome renders at once), `FailureNotice` when the read fails, `EmptyState` when it settles empty. Read the query's `status`, not an empty list. An action's own pending label (`pendingLabel`, a Load action's `Loading…`) is feedback, not loading copy | A skeleton, shimmer, placeholder rows or loading copy (`check:platform-design` `loading-copy`); a pending hint, placeholder or detail line; treating empty as loading |
24
24
| Failed read | `FailureNotice`, labelled Retry. `intent='warning'` where the read partly answered | A hand-built retry button or `InlineNotice` + `Button` |
25
25
| Failed background refresh | `FailureNotice` above the content it keeps | Replacing content already on screen |
26
26
| Empty | `EmptyState`, with no create button when the page already has one. A picker's no-match line inside a dialog stays `Muted`, since a block would take over the dialog | `Muted` or a raw `<p>` anywhere else |
27
27
| Confirm | `ConfirmDialog` (`target`, `title`, `confirmLabel`, `pendingLabel`, `onConfirm`, `safe` when nothing is lost). Removing a person, revoking an invite and any destructive row action always ask first | `Modal variant='confirm'`, `window.confirm`, an unconfirmed destructive action |
28
-
| Save | `SaveActions`, end-aligned in the page flow under the fields it commits: Discard while dirty, then Save. Save is visible and disabled while clean | Save in a header, Save before Discard, a Cancel beside Discard |
28
+
| Save | `SettingsSaveBar` (`SaveActions` plus the leave guard), end-aligned in the page flow under the fields it commits: Discard while dirty, then Save. Save is visible and disabled while clean. A create form keeps its own Create {noun} or Add {noun} submit | `SaveActions` or a Discard button outside the bar (`save-owner`), Save in a header, Save before Discard, a Cancel beside Discard |
29
29
| Autosave vs Save | One or the other per page. A page with `SaveActions` holds every field in one draft and writes it in one request. List actions with their own dialog, such as adding or removing a member, are not field edits | A page mixing both |
30
30
| Create | A primary compact button in `SectionToolbar`, or in the page header on a page with one | Create in both the toolbar and the empty state |
31
31
| Search over a section's rows | `SearchField` in the toolbar's `grow` slot, labelled and placeholdered `Search <subject>…` | A hand-built input, or copy that names more than the rows it filters |
32
32
| Row | `ActivityRow` for a row that opens something, `SettingsRow` for a row holding a control | A hand-built row |
33
-
| List of rows | `RowList` (`dividers` for a list read as one block). `ActivityRow density='comfortable'` in settings | `calc(-1 * var(--ui-row-inset))` around a list; styling a row's height from outside |
33
+
| List of rows | `ResourceList`, which bleeds through `PageOverflow`. `ActivityRow density='comfortable'` in settings | `calc(-1 * var(--ui-row-inset))` around a list; styling a row's height from outside |
34
+
| Empty settings list | `SettingsList` with `empty`: the `EmptyState` in place of the surface, under the section title | An `EmptyState` inside a `SettingsSurface` or `SettingsSection` (`empty-in-surface`) |
35
+
| On-or-off setting | `SwitchRow` | A `Switch` in a `SettingsRow` (`switch-row`) |
34
36
| Row actions | `ActionMenu` with icon `I108`, destructive item last with a separator | The same action in the row and its menu |
35
-
| Section heading | `SettingsSection title` (h2), `DetailHeader` for a sub-view's way back | A hand-written heading |
37
+
| Page heading | `SettingsPageHeader` in a section's main view, `DetailHeader` in a sub-view: exactly one per page, in every state | A heading in the frame, a sub-view's title under the page's own |
| Open a sub-view | `useSettingsSubview`: the record's id, or the create flow, in the URL | A detail or create flow in `useState`, which Back and refresh lose |
40
+
| One read | `SettingsRead` with the query and its failure message | A hand-built pending, failure and refresh-failure branch |
36
41
| Toolbar | `SectionToolbar grow={<SearchField label='Search members' … />}` with its actions as children | A hand-built flex row |
37
42
38
43
## Failure feedback
@@ -58,5 +63,7 @@ user activation is spent, `cancel` cannot be prevented, so `OverlayDialog` reope
58
63
59
64
- A section with more than one job splits by job into sibling files, as `settings/_components/sections/access-control/`
60
65
does (`group-detail.tsx`, `permission-rows.tsx`, `add-members-modal.tsx`).
61
-
- What organization and workspace Settings share is in `_components/settings-parts/`
62
-
(`apps/sim/platform/docs/component-ownership.md`); a view's data and logic follow `AGENTS.md` "Data seam".
66
+
- What organization and workspace Settings share is in `_components/settings-parts/`: `SettingsBody` and
67
+
`SettingsPageHeader` (`settings-page.tsx`), the parts in `settings-parts.tsx`, and saved preferences in
Open the panel/modal only when the id **resolves to a loaded entity** — never gate on the raw param alone, or a dead/stale id (deleted entity, old bookmark) renders a broken detail view and a still-loading list flashes one. A dead id simply falls back to the list; the lingering param is harmless. Because this reads `useSearchParams` it needs a **Suspense** boundary on the page (see "Suspense boundary" above). A separate "create new" flow has no id and stays in local `useState`.
246
+
Open the panel/modal only when the id **resolves to a loaded entity** — never gate on the raw param alone, or a dead/stale id (deleted entity, old bookmark) renders a broken detail view and a still-loading list flashes one. A dead id simply falls back to the list; the lingering param is harmless. Because this reads `useSearchParams` it needs a **Suspense** boundary on the page (see "Suspense boundary" above). A separate "create new" flow has no id and stays in local `useState`, except in Platform Settings, where a section opens its record and create sub-views through `useSettingsSubview` (`apps/sim/platform/lib/data/settings-subview.ts`), which keeps both in the URL with this push/replace convention.
247
247
248
248
**Close with `replace`, open with `push`.** Opening pushed a history entry; closing must not push another. Close via the setter's per-call options — `setSkillId(null, { history: 'replace' })` — so Back from the list leaves the page instead of reopening the detail (see `mcp-section.tsx`, `custom-blocks-section.tsx`, `forks-section.tsx`). Secondary params scoped to the detail view (e.g. its active tab, `server-tab`) are cleared in the same close handler with their own setter — nuqs batches same-tick writes into one URL update.
0 commit comments