Skip to content

Feature: Space settings as a panel and split into tabs - #164

Merged
jhweir merged 1 commit into
devfrom
feat/space-settings-panel
Aug 27, 2026
Merged

Feature: Space settings as a panel and split into tabs#164
jhweir merged 1 commit into
devfrom
feat/space-settings-panel

Conversation

@jhweir

@jhweir jhweir commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

Space settings as a panel, in four tabs

Summary

The chrome rail's gear did not open space settings — it opened global settings and then
navigated, inside that overlay's own memory router, to the page for whichever space you happened to
be in (openShellView('settings', '/spaces/<id>')). So a button labelled "Space settings" produced a
full-window surface headed "Settings", with an Account / Appearance / Spaces & data nav down its
side, and took the space off the screen while you configured it. Every part of that reads as an exit
from the space rather than a layer over it. It was also the one settings surface that could not
follow you: navigateToSpace closes shell views, so walking to another space closed the settings you
had open for the last one.

It is a dock now — registered by the shell exactly the way editorDocks.ts registers the
editor's four panels, against hostDockStores.shell. That inherits the entire panel contract for
free: drag and eight snap positions, resize from any edge, float or displace, maximise, one close
button on one titlebar, a $surface boundary so the contents adapt to the panel rather than the
window, and a placement remembered per device. The rail's gear toggles it and lights up while it is
open, like every other row in that rail.

The page in Settings → Spaces & data stays, because configuring a space you are not standing in is
a different act the panel cannot express. Both hosts render the same nodes: spaceSettingsBody takes
the space to configure and the chrome to sit above it. And both are now tabbed — About,
Appearance, Features, Vocabulary — because flat this was nine cards above a vocabulary
section of some two thousand lines, which is a long scroll on a page and an unusable one in a 440px
panel.

Changes

packages/templates/shell/src/spaces/SpaceSettings.ts

Split into a parameterised body and a tab structure.

  • spaceSettingsBody(uuid, chrome, fill?) replaces the hardcoded spaceSettingsPage. The space was
    read straight off routeStore.segments.1, which a dock cannot supply — a panel has no router. The
    page passes the route segment; the panel passes datasetStore.currentDataset.id.
  • spaceIdentity factored out of the old header. The page pairs it with a back link and an "Open"
    button; the panel shows it alone, since you are already in the space.
  • Four tabs, by subject, held in one $localState field. $if rather than $animate per tab, so
    the tab you are not looking at costs nothing — Vocabulary alone carries three live subscriptions
    and the model wizard. Safe to unmount here because the name and description fields save on blur,
    and clicking a tab is the blur.
  • The audience grouping is preserved rather than dropped. The three flat groups ("Everyone in this
    space" / "What this space has" / "Just for you, here") were answering "who else sees this change?",
    which subject tabs do not answer on their own. Appearance and Features each mix two audiences and
    keep the existing groupHeading rules; About and Vocabulary do not, and get a one-line
    audienceNote instead. The two switches per module row stay side by side for the same reason they
    always did.
  • fill threads the panel's shape through: the tab strip pins and the open tab scrolls inside a
    we-scroll-area, so the tabs stay reachable in a box a few hundred pixels tall. The page wants the
    opposite — it scrolls as a whole inside the overlay — so a scroll region there would be a second
    scrollbar inside the first.

packages/templates/shell/src/spaces/SpaceSettingsPanel.schema.ts (new)

The dock's node: the panel root every docked panel uses (width/height: 100%, overflow: hidden),
spaceIdentity as its header, and the shared body keyed on the open dataset.

Carries a noSpaceOpen state. It is reachable and not only in theory — the gear exists only inside a
space, but the panel is the user's to leave open, and the spaces list, a profile overlay or an
embedded app all take the current dataset away underneath it. Said rather than self-closed: a panel
that vanished on leaving a space would take its position and size with it and offer no way back.

Named .schema.ts deliberately, following the note in module-system/transcribe/src/Panel.schema.ts
— the validator walks files by that name and descends into their imports, and nothing imports a
dock node (it is reached from a registry), so under any other name a typo'd prop here would surface
only as a panel silently not rendering.

packages/app-shell/src/shared/registries/shellDocks.ts (new)

registerShellDocks(), the sibling of registerEditorDocks(). One dock so far. No size key so the
host's md applies (440px, what every other panel opens at); no float key so it opens displacing,
which is the point of not being an overlay.

Called from registerCoreSlots, after the editor's panels and before the chrome rail.

packages/app-shell/src/frameworks/solid/stores/ShellStore.tsx

spaceSettingsOpen, spaceSettingsEdge, and open/close/toggle. Shell state for the reason
createSpaceOpen is: two controls open it (the rail's gear and the About view's pencil), so it
belongs to neither.

The store registers itself as a host dock store at the end of the provider — after store exists
and after onDockRegistryChanged is subscribed, since announcing into a listener that is not yet
added is the failure dockRegistry's own docblock describes. Safe despite this being the store that
resolves docks: dockRequests reads the accessor and the accessor writes nothing.

packages/templates/shell/src/ChromeRail.schema.ts

The gear toggles the panel and binds active to shellStore.spaceSettingsOpen. It was comparing
activeShellView === 'settings', which also lit up for settings reached from the spaces list.

packages/templates/views/src/views/AboutView/index.ts

The pencil calls openSpaceSettings — opens rather than toggles, because a pencil sitting on the
fields it leads to is a promise to show them, and a second press landing on a closed panel would
break that for anyone who already had it open.

It no longer passes an id, which retires a real trap the old comment documented at length:
/space/:spaceId carries a neighbourhood CID for a shared space while the settings page keys off the
dataset id, so the obvious spelling opened an empty page, and only for shared spaces.

packages/app-shell/src/shared/registries/templateSurface.ts

Classifies the four new members. navigation for all of them, on the reasoning already recorded for
setCreateSpaceOpen: they ask for a host surface and nothing more, and what that panel then permits
is decided inside it by canAdminister. spaceSettingsEdge is WIRING — read by the dock resolver
in TypeScript, never by a schema.

Space-tier rather than chrome-tier is load-bearing: the About view carries the pencil, and a view
renders at SPACE_TIER, so chrome-tiering would leave that button pointing at something it is not
allowed to call.

packages/ai-context/src/fragments/stores.ts + regenerated files

Documents the new store members, with CLAUDE.md, copilot-instructions.md, we-schema.mdc,
schemaContext.ts, context.json and contextData.ts regenerated together. Without the regenerated
contextData.ts the validator reports every new $store/$action reference as an unknown member.

packages/app-shell/tests/moduleRegistry.test.ts

Fixture: the core slot ordering assertion enumerates every id, so the new dock joins the list.

Known follow-ups

  • Marketplace browsers in a 440px panel. spaceDefaultsSection pulls in the template and theme
    marketplace browsers, which were laid out for a full page. They scroll and are usable, but the
    panel is where they now most often get seen — worth a look at their narrow rendering.
  • The panel does not close itself when you leave a space. It cannot from where the state lives:
    ShellStoreProvider is the outermost store, above DatasetStoreProvider, so it cannot read the
    current dataset without plumbing a push-in accessor. The noSpaceOpen state covers it honestly and
    the panel recovers on its own the moment a space is open again.
  • The tab does not persist. Deliberate for now — restoring Vocabulary onto a space that has none
    is a worse first frame than starting at About — but if the tab turns out to be sticky in practice,
    persist is one field away.

Test plan

  • pnpm --filter @we/schema-shared validate — 31 schemas, no issues (was 30; the new panel is
    picked up by name).
  • pnpm build — clean, exit 0.
  • pnpm typecheck — clean across the workspace.
  • pnpm test — every package passes; @we/app-shell 42 files / 608 tests.
  • pnpm lint — clean.
  • Manual — not run. Worth checking in the app: the gear toggling the panel and lighting up;
    dragging, snapping, resizing and maximising it; the tab strip staying pinned while a tab
    scrolls; the model wizard and signal-type modals opening from inside a floating (glass) panel,
    which is the case where the panel becomes a containing block for fixed descendants; walking
    between spaces with the panel open; leaving a space entirely and coming back; and the settings
    page in Settings → Spaces & data still working, tabs and all, for a space you are not in.

@netlify

netlify Bot commented Aug 27, 2026

Copy link
Copy Markdown

Deploy Preview for coasys-we ready!

Name Link
🔨 Latest commit a515a53
🔍 Latest deploy log https://app.netlify.com/projects/coasys-we/deploys/6a8fde8a1864a5000897f890
😎 Deploy Preview https://deploy-preview-164--coasys-we.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

The chrome rail's gear called openShellView('settings', '/spaces/<id>') —
which opened *global* settings and then navigated, inside that overlay's own
router, to the page for the space you were in. A button labelled "Space
settings" produced a full-window surface headed "Settings" with an Account /
Appearance / Spaces & data nav down its side, and took the space off the
screen while you configured it. It was also the one surface that could not
follow you: navigateToSpace closes shell views, so walking to another space
closed the settings you had open for the last one.

It is a dock now, registered by the shell the way the editor registers its
four panels, so it inherits the whole panel contract for nothing: drag and
eight snaps, resize, float or displace, maximise, one close button on one
titlebar, and a placement remembered per device. The rail's gear toggles it
and lights up while it is open, like every other row in that rail.

The page in Settings → Spaces & data stays, because configuring a space you
are *not* standing in is a different act the panel cannot express. Both
render the same nodes: spaceSettingsBody takes the space to configure and the
chrome to put above it, so there is one definition rather than two that
drift.

Both are now tabbed — About, Appearance, Features, Vocabulary. Flat, this was
nine cards over a vocabulary section of some two thousand lines, which is a
long scroll on a page and an unusable one in a 440px panel. The tabs are by
subject; the audience question the three old groups answered moves inside
them, as the same group headings on the two tabs that mix and a one-line note
on the two that do not.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jhweir
jhweir force-pushed the feat/space-settings-panel branch from f8e4a9a to a515a53 Compare August 27, 2026 06:51
@jhweir
jhweir merged commit 025f52f into dev Aug 27, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant