Feature: Space settings as a panel and split into tabs - #164
Merged
Conversation
✅ Deploy Preview for coasys-we ready!
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
force-pushed
the
feat/space-settings-panel
branch
from
August 27, 2026 06:51
f8e4a9a to
a515a53
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 afull-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:
navigateToSpacecloses shell views, so walking to another space closed the settings youhad open for the last one.
It is a dock now — registered by the shell exactly the way
editorDocks.tsregisters theeditor's four panels, against
hostDockStores.shell. That inherits the entire panel contract forfree: drag and eight snap positions, resize from any edge, float or displace, maximise, one close
button on one titlebar, a
$surfaceboundary so the contents adapt to the panel rather than thewindow, 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:
spaceSettingsBodytakesthe 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.tsSplit into a parameterised body and a tab structure.
spaceSettingsBody(uuid, chrome, fill?)replaces the hardcodedspaceSettingsPage. The space wasread straight off
routeStore.segments.1, which a dock cannot supply — a panel has no router. Thepage passes the route segment; the panel passes
datasetStore.currentDataset.id.spaceIdentityfactored out of the oldheader. The page pairs it with a back link and an "Open"button; the panel shows it alone, since you are already in the space.
$localStatefield.$ifrather than$animateper tab, sothe 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.
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
groupHeadingrules; About and Vocabulary do not, and get a one-lineaudienceNoteinstead. The two switches per module row stay side by side for the same reason theyalways did.
fillthreads the panel's shape through: the tab strip pins and the open tab scrolls inside awe-scroll-area, so the tabs stay reachable in a box a few hundred pixels tall. The page wants theopposite — 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),spaceIdentityas its header, and the shared body keyed on the open dataset.Carries a
noSpaceOpenstate. It is reachable and not only in theory — the gear exists only inside aspace, 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.tsdeliberately, following the note inmodule-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 ofregisterEditorDocks(). One dock so far. Nosizekey so thehost's
mdapplies (440px, what every other panel opens at); nofloatkey 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.tsxspaceSettingsOpen,spaceSettingsEdge, and open/close/toggle. Shell state for the reasoncreateSpaceOpenis: two controls open it (the rail's gear and the About view's pencil), so itbelongs to neither.
The store registers itself as a host dock store at the end of the provider — after
storeexistsand after
onDockRegistryChangedis subscribed, since announcing into a listener that is not yetadded is the failure
dockRegistry's own docblock describes. Safe despite this being the store thatresolves docks:
dockRequestsreads the accessor and the accessor writes nothing.packages/templates/shell/src/ChromeRail.schema.tsThe gear toggles the panel and binds
activetoshellStore.spaceSettingsOpen. It was comparingactiveShellView === 'settings', which also lit up for settings reached from the spaces list.packages/templates/views/src/views/AboutView/index.tsThe pencil calls
openSpaceSettings— opens rather than toggles, because a pencil sitting on thefields 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/:spaceIdcarries a neighbourhood CID for a shared space while the settings page keys off thedataset id, so the obvious spelling opened an empty page, and only for shared spaces.
packages/app-shell/src/shared/registries/templateSurface.tsClassifies the four new members.
navigationfor all of them, on the reasoning already recorded forsetCreateSpaceOpen: they ask for a host surface and nothing more, and what that panel then permitsis decided inside it by
canAdminister.spaceSettingsEdgeisWIRING— read by the dock resolverin 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 notallowed to call.
packages/ai-context/src/fragments/stores.ts+ regenerated filesDocuments the new store members, with
CLAUDE.md,copilot-instructions.md,we-schema.mdc,schemaContext.ts,context.jsonandcontextData.tsregenerated together. Without the regeneratedcontextData.tsthe validator reports every new$store/$actionreference as an unknown member.packages/app-shell/tests/moduleRegistry.test.tsFixture: the core slot ordering assertion enumerates every id, so the new dock joins the list.
Known follow-ups
spaceDefaultsSectionpulls in the template and thememarketplace 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.
ShellStoreProvideris the outermost store, aboveDatasetStoreProvider, so it cannot read thecurrent dataset without plumbing a push-in accessor. The
noSpaceOpenstate covers it honestly andthe panel recovers on its own the moment a space is open again.
is a worse first frame than starting at About — but if the tab turns out to be sticky in practice,
persistis one field away.Test plan
pnpm --filter @we/schema-shared validate— 31 schemas, no issues (was 30; the new panel ispicked up by name).
pnpm build— clean, exit 0.pnpm typecheck— clean across the workspace.pnpm test— every package passes;@we/app-shell42 files / 608 tests.pnpm lint— clean.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.