Skip to content

Commit 8f3b45b

Browse files
committed
fix(org): keep the panel's skill timing and bound Enter while lists load
1 parent 2733686 commit 8f3b45b

7 files changed

Lines changed: 930 additions & 129 deletions

File tree

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
---
2+
description: One pattern per job across the organization views (Settings, People, Integrations, Links, Home)
3+
paths:
4+
- "apps/sim/platform/app/(shell)/{settings,people,integrations,links,home}/**"
5+
- "apps/sim/platform/design-system/components/{status,modal,picker,settings-section,search-field,activity-row}/**"
6+
- "apps/sim/app/o/*/**"
7+
---
8+
9+
# Organization Views
10+
11+
The organization views live in `apps/sim/platform/app/(shell)/` and take everything shared from
12+
`@/platform/design-system/components`. Each job below has one pattern. A view that differs needs a
13+
reason written where it differs; "it was already like that" is not one.
14+
15+
| Job | Use | Never |
16+
| --- | --- | --- |
17+
| First read of a view | `QueryBoundary` (`query`, `failure`, `loadingLabel`, optional `empty`, `loading`), which renders `LoadingRows`, then `FailureNotice`, then `empty`, then `children(data)` | Inline `isPending ? … : error ? …`, a raw `<p>Loading…</p>`, a loading `InlineNotice` |
18+
| Loading | `LoadingRows` by default. A placeholder shaped like its content (`WidgetSkeleton`, usage charts) only where rows would misrepresent it | A second generic skeleton |
19+
| Failed read | `FailureNotice`, labelled Retry. `intent='warning'` where the read partly answered | A hand-built retry button or `InlineNotice` + `Button` |
20+
| Failed background refresh | `FailureNotice` outside the boundary, over the content it keeps | Replacing content already on screen |
21+
| 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 |
22+
| 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 |
23+
| 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 |
24+
| Editing a server value | `useServerDraft`. Adopts a newer value while clean; while dirty it keeps the edits and sets `conflict`. Show `DraftConflictNotice`, and block Save until the viewer chooses | Seeding `useState` from a query once |
25+
| 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 |
26+
| 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 |
27+
| Search over a section's rows | `SectionSearch subject='members'` | `SearchField` with its own copy, unless the search covers more than the subject's name |
28+
| Row | `ActivityRow` for a row that opens something, `SettingsRow` for a row holding a control | A hand-built row |
29+
| 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 |
30+
| Row actions | `ActionMenu` with icon `I108`, destructive item last with a separator | The same action in the row and its menu |
31+
| Section heading | `SettingsSection title` (h2), `DetailHeader` for a sub-view's way back | A hand-written heading |
32+
| Toolbar | `SectionToolbar grow={<SectionSearch />}` with its actions as children | A hand-built flex row |
33+
34+
## Failure feedback
35+
36+
- Form validation is inline, on the field (`error`), once a save has been attempted.
37+
- A dialog's own action failing stays in the dialog: `Modal` shows a rejected `onConfirm`'s message and keeps the draft.
38+
- Any other action failing is a toast. Nothing fails silently.
39+
40+
## Page gates
41+
42+
Every page under `app/o/[organizationId]` calls
43+
`requireOrganizationViewer(organizationId, callbackUrl)` from `@/app/o/[organizationId]/viewer`. It
44+
redirects a signed-out viewer to sign-in and back, 404s a viewer with no access, and returns
45+
`{ userId, principal, surface }`. Never write `{ kind: 'session', ... }` in a page.
46+
47+
## Dialogs
48+
49+
`Modal` holds itself open while `onConfirm`'s promise or its `pending` prop runs. Escape, the backdrop
50+
and Close do nothing until it settles, including when the browser closes the `<dialog>` itself: once
51+
user activation is spent, `cancel` cannot be prevented, so `OverlayDialog` reopens from `close`.
52+
53+
## Files
54+
55+
- A section with more than one job splits by job: `group-detail.tsx` is the shell, `group-general-tab.tsx`
56+
and `group-catalog-tabs.tsx` its bodies, `use-group-draft.ts` its write.
57+
- Merge logic worth a test is a pure module next to the view, such as `secrets-draft.ts`.
58+
- `section-parts/` holds what only settings needs: `SectionStack`, `FieldStack`, `SectionToolbar`,
59+
`DetailHeader`, `HeadingRow`, `Muted`, `SectionSkeleton`, `DraftConflictNotice`. `useServerDraft` is in `_components/`, since Integrations uses it too.
60+
61+
## Legacy workspace settings
62+
63+
`apps/sim/app/workspace/*/settings/**` keeps `SettingsPanel` from `@/components/settings/settings-panel`,
64+
which owns chrome through `settings/[section]/layout.tsx`. The `add-settings-page` skill describes it.
65+
New organization work never imports it.

0 commit comments

Comments
 (0)