Spaces UI - #12
Draft
calebroseland wants to merge 28 commits into
Draft
Conversation
Soft-merge of MM-69892 (PR #7, nang2049): the editor slice in webapp_globals and a stub PageEditor mounted at the page/draft routes, so the spaces-API wiring can build around the editor and avoid later conflicts. Applied as a net diff onto master since PR #7 was cut from the pre-merge scaffolding branch. Two divergences resolved: - Dropped PR #7's navigation.ts change: master's merged draft-route fix already exposes the isDraft the editor consumes (single [DOCS_DRAFT_ROUTE, DOCS_ROUTE] match instead of two useRouteMatch calls). - webapp_globals: took PR #7's superset React type imports; kept master's useBootstrapDocs in docs_root (3-way merged cleanly with the isDraft add).
Replace the mock data source with an API-backed DocsDataSource over the
plugin's /api/v1 routes (server/api.go), following the Playbooks
fetch(url, Client4.getOptions()) idiom. Spaces are created, listed
(team-scoped by backing-channel membership), and left against the real
server; the switcher fans out per team for cross-team results.
- client/rest: doGet/doPost/doDelete + paginated listAll ({items,has_more})
- data: async DocsDataSource + apiDataSource; remove mock fixtures
- store: fetchSpaces/fetchAllSpaces/createSpace/leaveSpace async thunks;
drop the team_id hashing stand-in (the server scopes lists by team)
- opaque server ids: drop slug-as-id and isSlugAvailable; the slug stays a
client-only vanity field, format-validated only
- recent: client-side view history (data/recent_spaces) as the seam for a
later server last_viewed_at proxy; page count omitted for MVP
- leave space: wired to member removal, navigates home when viewing it
- visibility stays client-only, maps to server view_access later (PR #10)
Addresses PR #2 feedback (team_id hashing, slug assumption, leaveSpace
wiring). Targets current master; forward-compatible with PR #10.
Adds the docs.editor.* ids from the stub editor and drops the removed url.taken and page-placeholder ids plus the stale favorites ids.
The slug/URL field became dead weight once spaces route by opaque server id: it was collected and format-validated but never sent or used. Remove it from the create-space modal and the form/schema — slug is gone from CreateSpaceInput, the form values, and the Zod schema (with its Url* errors and slugify helper). The UrlInput form-control component and its test are kept for reuse. Re-extract en.json (drops the url.* messages).
Custom emoji/icon picking is deferred, so a space without a custom icon renders a generic compass glyph (utils/space_icon: <SpaceIcon>, using the product's file-text-outline) everywhere a space icon shows — sidebar, home cards, main content, switcher, and the create-space name field — so a freshly created space never appears blank. A space with a custom emoji renders that character. Switcher: rename the "Recent docs" group to "Recent", and dedupe — a space already listed under Recent is no longer repeated under "Your spaces".
Reverts 13b2db3 (soft-merge of PR #7's stub editor): the mounted editor wasn't working. Drops the page_editor component, the editor slice from webapp_globals, and the pageId->PageEditor routing in docs_main_content (back to the space/page placeholder). Later work on the touched files (SpaceIcon, useRecordSpaceView) is preserved. Re-extract en.json.
Implements the space main-content view (Figma Space Home) as a new space_view component set, replacing the routed-space placeholder: - SpaceTitleBar: favorite, space icon + title + menu, member count, space details, Share - PageBar: page-tree toggle + "Pages", "Updated <relative>" (host Timestamp), comments, Edit, overflow, expand - PageHero: rounded banner with the space icon/title, description (with a placeholder when empty), and a pages/members/views stats row - Page body: a placeholder until the editor is mounted Uses Mattermost theme tokens and compass icons. Data-less bits are honest placeholders for now: the stat counts and title-bar member count render an em dash (await the pages/members/views APIs), member avatars are omitted, and the controls are visual scaffolding wired in later passes.
Feeds the Space Home stats and title-bar member count with real data.
- data: add listSpaceMembers over GET /spaces/{id}/members; normalize the
page-list summaries (no body) to Page for the store
- store: new spaceMembers slice (user ids per space) + fetchSpaceMembers;
fetchPages now stores pages for the count (reused by the page tree later)
- selectors: getSpaceMemberIds; page count derives from getPagesForSpace
- hooks: useSpaceStats loads both on space mount and returns the counts
- space view: real PAGES and MEMBERS counts in the hero and the title-bar
member count
Views has no server source yet, so that stat stays an em dash.
Space view build-out plus a raw-<button> cleanup. - buttons: replace the raw <button>s in the space title/page bars with the shared Button — icon buttons use emphasis="quaternary" + the compass `btn-icon` class (the core pattern), Edit uses SecondaryButton - avatars: expose the host Avatar via webapp_globals (as a wrapper that no-ops when the host doesn't publish it, so callers need no null check); useSpaceMemberProfiles resolves member ids to profiles (fetching missing ones); MemberAvatars renders the overlapping stack + "+N" in the hero - share: ShareSpaceModal — real member list with avatars/names and a functional Copy link; the add-people search, per-member role, and space visibility/access are scaffolding (capabilities + view_access land with PR #10). Wired to the title-bar Share button.
Adds the space page-tree panel (Figma) as a middle column in the space view, backed by the real page + move APIs. - store: buildPageTree (parent_id/sort_order, orphans as roots) + buildDescendantMap; reindexAfterMove (0-based sibling renumber mirroring the server) + MOVED_PAGE; movePage thunk (optimistic move, reconcile on success, re-fetch on failure) and createPage thunk - data: DocsDataSource.movePage (PATCH .../move) and createPage (POST); restPatch - collapse: per-user localStorage tracker (data/collapsed_pages) + useCollapsedPages, mirroring the recent-spaces seam - ui: PageTreePanel (264px column: Pages header, add-page, tree) and the recursive PageTreeNode (disclosure chevron, page/folder glyph, indent, active highlight, navigate/collapse) - dnd: pragmatic-drag-and-drop reused from spaces_sidebar — a 3-zone hitbox (reorder above/below, reparent onto center), descendant-drop guard, and a monitor resolving the drop into (parent_id, sibling_index) - layout: space view is now the title bar over [tree panel][content column]; the page-bar Pages button toggles the panel - tests: buildPageTree/descendants and reindexAfterMove/MOVED_PAGE Design's role/visibility remain PR #10 scaffolding; the page body is still a placeholder until the editor is mounted.
Adds a real debounced people-search pipeline and a Base UI Autocomplete combobox for the Share modal, gated on a named permission so nothing misleading ships. There is no server add-member API yet (roles/view-access + capabilities land with PR #10), so a live "add people" control could only hold selections client-side. The picker renders only when canManageMembers. - store/permissions: getSpacePermissions(state, spaceId) selector + useSpacePermissions(spaceId) hook — the get/use split from core's channel_bookmarks/utils — returning named SpacePermissions {canManageMembers}; the seam PR #10's capability set drops into - hooks/user_search: useUserSearch — 300ms-debounced searchProfiles scoped to the current team, mapped to MemberProfile, excludes already-shown ids - share_space_modal/people_picker: Base UI Autocomplete (server-driven list, host Avatar rows, empty/searching states) - share_space_modal: render the picker only when canManageMembers
- permissions: a DEV override forces canManageMembers on so the add-people picker is exercisable before PR #10's capability set exists. The get/use permission hooks are unchanged; revert the default to false (and wire the real capability source) before shipping. - space title bar: the member count is now a button that opens the Share modal, the same as the Share button.
The shared Button is accent-only: every emphasis (quaternary etc.) colors with --button-bg, and its rule follows .btn-icon in core's _buttons.scss, so `<Button emphasis='quaternary' className='btn-icon'>` renders the canonical icon markup but in accent blue instead of neutral grey. Add a :global override that raises .btn-icon's specificity over the emphasis so icon buttons stay neutral (center-channel-color), loaded as a side effect via the button wrapper so it applies everywhere. Marked PORT-TO-CORE — fold into core's .btn-icon rule and delete when it lands.
Button styling: - Pages toggle and Edit are neutral ghost buttons now (not accent-blue / bordered secondary). Edit is an icon+label ghost (pencil + "Edit"). - Members count and the favorite star use btn-xs, closer to core's channel-header favorite toggle; Members reads neutral. - Generalize the port-to-core neutral treatment to also cover labeled quaternary ghost buttons via a `docs-btn-neutral` marker, alongside the existing `.btn-icon` rule (renamed button_icon_neutral -> button_neutral). Rename components/form-controls -> components/form_controls (underscore, matching the rest of the tree), updating all imports.
btn-quaternary hovers with an accent-blue wash; the neutral icon/label ghost buttons now use the grey channel-header hover instead.
Space management from the title bar, backed by the real update/archive APIs.
- data/store: updateSpace (PATCH /spaces/{id}) and deleteSpace (DELETE)
data-source methods + thunks; UpdateSpacePatch type
- Space Settings modal (space_settings_modal): tabbed (Info / Permissions /
Configuration / Archive). Info edits name+description via updateSpace;
Archive confirms then deleteSpace + goHome. Permissions/Configuration are
PR #10 scaffolding (view_access, roles, external sharing).
- Space Info panel (space_info): read-only RHS mirroring core's Channel Info
— icon/title, description, members (avatars), page count + created time;
renders as the right column of the space view.
- title bar: the space name opens a dropdown menu (Space info, Members, Copy
link, Space settings, Leave, Archive; Settings/Archive gated on
canManageMembers). The details button is now the Info-panel toggle.
Built via subagents from an on-disk plan; reviewed and gates re-run here.
Neutral toolbar buttons now mirror core's .btn-icon / channel-header icon: grey base, light-grey hover, light-blue press (:active, declared after :hover so a hovered press reads blue), and persistent blue when toggled on (.active). The Info button gets `active` while its panel is open (alongside aria-pressed) so the toggled state reads deep blue.
Rename SpaceTitleBar -> SpaceHeader and PageBar -> PageHeader (files, components, scss modules, imports, and stale comments).
Stack space header (56px) and page header (52px, no bottom border) full width, then a body row of the pages sidebar over the page content column. The sidebar is always mounted and animates its width open/closed so the tree slides in/out without reflowing.
Raise preset-env targets to Chrome 110 / Firefox 102 / Edge 110 / Safari 16.4 to match the host channels webapp's documented minimum supported browsers.
Lazy-load the Docs backstage UI into its own async chunk (docs_root_lazy + React.lazy/Suspense) so the initial plugin bundle stays small. Switch webpack output to publicPath: 'auto' with chunkFilename, and emit binary assets via asset/resource instead of inlining, so chunks and assets resolve from the served plugin path (works under subpath hosting). Add a build:analyze target gated on webpack-bundle-analyzer.
Extract a reusable Header (56px bar, bottom border, left/right slots) and adopt it in Docs Home and the space header, unifying the top-bar chrome. Docs Home's header is now 56px tall to match.
Large batch of Spaces UI work against the Figma specs, plus the shared infrastructure it needed. Shared UI: - Menu refactored to children-based compound components (Item, LinkItem, Separator, Submenu) and all callsites migrated - Toast and modal controllers on base-ui, with imperative + hook APIs - Tabs (vertical/horizontal), Select, Spacer, SaveChangesBar, BasicInputModal, ResizableDivider; GenericModal gains titleActions and optional header/footer dividers - Button drops the required `type`, gains badging and icon/label modes - WithTooltip is now the only tooltip; local tooltip component removed Space view: - Space header 56px, page header 52px, page tree, page content and hero - Real resizable pages sidebar and full-height Space Info panel, with editable description, menu and members sub-panel - Page menu (tree + header), rename and delete modals, page drag preview - Overview row for space home; truthful canDrop with blocked drop cues Favorites: - Spaces and pages favorited via the user preferences API, with memoized selectors, tri-state space toggle and per-team sidebar ordering - Ordering payload capped under the preference value limit Data layer: - Plugin reducers wrapped in an `entities` subtree - Default-page routing via canonical Switch/Route/Redirect, with an explicit /overview sub-route that outranks the default page Context: iterative UI wiring session on docs-wire-spaces-api, driven by Figma reviews and screenshots; verified with tsc, eslint, jest and build.
Restructure the tree so `role="treeitem"` sits on the node wrapper that owns the child group, giving the tree a single tab stop (roving tabindex) with arrow-key traversal, Alt+arrow reordering, and Shift+F10/Menu to open a row's actions. Reorder intent resolution lives in a pure `keyboard.ts` module so the depth and sibling rules are testable without the DOM. Add a `Readout` live region for the announcements this needs (moves, blocked moves, create/move failures), and controlled open state on Menu/PageMenu so a row can open its own menu from the keyboard. Also folded in while touching these paths: - gate the Home welcome hero on spaces actually being loaded, so it no longer flashes for returning users - extract `useLeaveSpace`, and surface a toast when archiving a space fails - fix sidebar order to use prefixed favorite keys for both lists - pass the resolved page into PageContent instead of re-selecting it - tests for the tree panel, keyboard rules, leave-space, actions, and stores Prompt: commit wip review changes
FetchOptions takes an optional AbortSignal, threaded through the rest helpers and listAll so an in-flight paginated load can be cancelled instead of running to the last page after the caller has moved on. Client4.getOptions already passes signal through to fetch, so no host-side wiring is needed. Aborts reject with fetch's AbortError rather than ClientError, keeping "I cancelled this" distinguishable from a server rejection. Prompt: FetchOptions should support an abort signal.
Conflicts in the webapp shell, resolved toward this branch's routing: - docs_main_content: kept the Switch/RoutedSpaceView routing over master's prop-drilled spaceId/pageId/isDraft placeholder, so page routes keep the space chrome (space header, page header, pages sidebar). - page_content: mounts master's PageEditor as the page body in place of the skeleton, so the editor stub stays wired without relocating the component. The skeleton still covers the window before the routed page is in the store. - docs_root: dropped the pageId/isDraft props DocsMainContent no longer takes; kept useRecordSpaceView. - webapp_globals: union of both sides' type imports. - i18n/en.json: regenerated with npm run i18n-extract rather than hand-merged (picks up docs.editor.*, drops the removed docs.main.spaceOverview). Prompt: pull in latest changes from master and resolve conflicts (our routing logic changes take priority, but don't move the page_editor component)
Pre-resolves the parts of the PR #13 (MM-69893) merge that are worth keeping whichever order the two PRs land in, so the editor branch can be merged, reverted and re-merged without redoing them. - webapp_globals: adopt the document-mode editor contract — contentType, extensions, onContentError, plus getEditor/hasContentError on the handle and a hostSupportsDocumentEditor probe. A Docs page body is structured content, so the editor is useless to us in markdown mode. Also onChange's param rename and ReactNodeArray -> ReactNode[], which drops a deprecation warning. - rest: thrown errors become RestError, a ClientError subclass that keeps the parsed payload and the server error id. Draft publish answers a 409 with the current page in the body, which ClientError alone discards. Existing instanceof ClientError / status_code callers are unaffected. - rest: add a path-relative doFetch over the same request path, for client modules that describe a whole endpoint family. - types: drop the stale note on Page — user_id and last_modified_by are here. The editor branch's client layer compiles against this without edits, so it no longer needs its own transport (which hand-rolled headers and relied on the deprecated XMLHttpRequest CSRF fallback). Prompt: merge PR#13 for preview with a revert-marked commit; conflict resolution in a separate commit that stays, to reduce conflicts when both PRs merge
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.
Summary
Space CRUD UI, Space Shell, Page Tree, etc
Ticket Link