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
refactor(resources): extend the axes for what every kind actually needs (#6391)
* refactor(resources): extend the axes for what every kind actually needs
Three gaps the file and log migrations never hit, all of which tables and
knowledge hit immediately. Extending the axis once beats four per-kind
workarounds, and each addition is uniform across every kind.
- `ResourceGrants.manage` — admin-only governance of the resource, as distinct
from writing its content. Table column locks are the first: an owner decides
which columns an editor may not touch, and the settings an editor is locked
out of are the ones that lock them out. `grantsFromPermissions` already
received `canAdmin` and dropped it on the floor.
- `ResourceGrants.settled` — whether the capabilities above are final. The one
member that describes the value rather than the viewer, and it has to live
beside them: a resolving membership and a genuinely denied one produce
identical booleans, so `write === false` could not be told from "not yet".
Surfaces that render an affordance disabled during load need that, and
one-shot latched effects need it badly — the table's lock notice fires once
and permanently loses its action if it fires before `manage` resolves.
Without this field both surfaces would have had to accept a first-paint
flicker; with it they stay byte-identical.
- `ResourceLink` gains `{ to: 'list' }` — the index route a kind lives under.
Every kind has one, every detail surface needs it (breadcrumb root, and the
redirect after the thing it was showing is deleted), and five call sites
across two route trees hand-built that path. `hrefFor` still returns null in
share scope, so the list route cannot be hand-built from a token either.
Knowledge's two list pushes now go through `hrefFor`. The table's two follow in
its own PR, once it builds a source.
Verified the new tests fail without the code: breaking `settled` to a constant
and pointing the list link at `resourceHref` turns three of them red.
* docs(resources): describe the extended axis where the rules already live
CLAUDE.md, .claude/rules and .cursor/rules all still spelled `grants` as
`{ write, run }` and `hrefFor` as self-or-resource. Adds `manage`/`settled` and
the `{ to: 'list' }` destination, plus the one thing a reader has to know about
`settled`: a denied member and a loading one produce identical capability
booleans, so `write === false` is not a decision until `settled` says it is.
Also splits the TSDoc that `resourceListHref` landed under — it was describing
`resourceHref` and would have documented the wrong function.
Import from the unit barrel (`@/components/resources/file-view`), never a file inside it. Scope-dependent copy lives on `source.unavailableCopy`; links on `source.hrefFor(link)` (which returns `null` in share scope). `hostOwnsUrl(host)` is the one place the "embedded views do not write nuqs keys" rule lives.
44
+
Import from the unit barrel (`@/components/resources/file-view`), never a file inside it. Scope-dependent copy lives on `source.unavailableCopy`; links on `source.hrefFor(link)` — `{ to: 'self' | 'resource' | 'list' }`, returning `null` in share scope. `hostOwnsUrl(host)` is the one place the "embedded views do not write nuqs keys" rule lives.
Copy file name to clipboardExpand all lines: CLAUDE.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -422,7 +422,7 @@ A **resource** is a thing a workspace holds that can also be shared — a file,
422
422
Views are mounted against exactly **three axes**, defined in `apps/sim/resources/**` (pure TypeScript — no React, no `'use client'`, because a Server Component builds a share source during SSR):
423
423
424
424
-`source` — where the data comes from and by what address: `WorkspaceSource<K> | ShareSource<K>`, discriminated on `via`. Replaces `workspaceId`, `token`, `contentSource`, `isPublic`. `ShareSource` declares `workspaceId?: never`, so a share token can no longer be laundered through a workspace-shaped slot.
425
-
-`grants` — what this viewer may do: `{ write, run }`. Replaces `canEdit`, `canRun`, `canAdmin`, `disableEdit/Insert/Delete`.
425
+
-`grants` — what this viewer may do: `{ write, run, manage }`, plus `settled` (whether those three are final, or still resolving — a denied member and a loading one are otherwise indistinguishable). Replaces `canEdit`, `canRun`, `canAdmin`, `disableEdit/Insert/Delete`.
426
426
-`host` — who owns the URL, the router, the document frame: `'page' | 'panel' | 'public'`. Replaces `embedded`. `hostOwnsUrl(host)` is the one place the "embedded views do not write nuqs keys" rule lives.
427
427
428
428
There is no fourth axis; agent streaming is one optional prop on `FileView`. Consumers CONSTRUCT the axes and MOUNT the view — never wrap it in a passthrough, never reach past its barrel, never reimplement its UI because it lacks a seam (add the seam), never import `@/app/workspace/[workspaceId]/**` from an anonymous surface (`app/f/**`, `app/(interfaces)/**`), and never read `useRouter`/`useParams`/`useQueryState`/`useUserPermissionsContext` inside a unit. A kind with no canonical view yet — `table` alone today — is simply absent from the view list in the check's `CANONICAL_UNITS` — no flag, shim, or placeholder. Every unit has the same layout (`<unit>.tsx` · `index.ts` · `components/<child>/` · `hooks/` · `utils/` · `types.ts`), so moving between them costs nothing.
Copy file name to clipboardExpand all lines: apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx
0 commit comments