Workspaces are not routeable — add workspace to the URL scheme (home filter + board routes) #213
Labels
No labels
prio_critical
prio_low
type_bug
type_contact
type_issue
type_lead
type_question
type_story
type_task
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lhumina_code/hero_whiteboard#213
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Workspaces are not routeable — add workspace to the URL scheme (home filter + board routes)
Summary
The app can deep-link to a board (
/board/{id}) but not to a workspace. Workspace selection exists only as client-side state on the home page, and board URLs carry no workspace context. This makes a selected workspace impossible to bookmark or share, and gives no navigation between a board and its parent workspace.Confirmed findings (current state)
Board.workspace_id(crates/hero_whiteboard_server/src/db/models.rs),board.createacceptsworkspace_id(handlers/board.rs),board.listfilters byworkspace_id(handlers/board.rs), andworkspace.list/workspace.getexist./,templates/web/home.html) already has a workspace dropdown (workspace-select), groups boards into per-workspace sections, and callsworkspace.list+board.list({workspace_id}). But the chosen workspace is held only in JS state — it is not reflected in the URL, so reloading or sharing the page loses it.crates/hero_whiteboard_admin/src/routes.rs) are limited to/,/board/{id},/board/{id}/view,/s/{token},/board/{id}/mindmap/{object_id}. There is no workspace route, and the board route has no workspace segment/param. The board page (web_board/BoardTemplate) does not surface which workspace the board belongs to.Goal
Make workspaces routeable and add workspace context to board navigation, reusing the existing backend (no schema or RPC changes expected):
Open design decision (to be settled in the implementation spec)
Exact URL scheme — options to weigh against the repo's
hero_ui_routesdeep-linking spec:/?workspace={id}) vs a dedicated workspace landing route (/workspace/{id})./workspace/{ws}/board/{id}) vs query (/board/{id}?workspace={ws}) vs resolving the workspace fromboard.getand adding a back-link without changing the board path.Acceptance criteria
/,/board/{id},/s/{token}) keep working with no regression.Notes
Conform to the repository's
hero_ui_routesUI routeability / deep-linking specification when choosing the scheme. Keep/s/{token}shared-board links workspace-agnostic.Implementation Spec for Issue #213
Objective
Make workspaces routeable so the home page's workspace filter and a board's parent-workspace context are encoded in the URL. No backend schema/RPC changes, no new server routes.
/s/{token}shared links stay workspace-agnostic; existing/and/board/{id}links keep working; all links remain base_path-prefixed.Chosen URL scheme
/?workspace={id}(/with no param = "All Workspaces").board.get(returnsworkspace_id); the board page renders a workspace breadcrumb linking back to/?workspace={workspace_id}. No workspace segment added to the board path.Rationale: the home page is a single collection filtered by workspace, so the canonical pattern (and what the
hero_ui_routesdeep-linking spec endorses for filtered collection views) is a query filter, not a separate route. This adds zero server routes and zero backend changes, avoids breaking existing/board/{id}links, and makes the board to workspace relationship a copyable link rather than hidden state.Files to Modify (frontend only)
templates/web/home.html- read?workspace=on load to seed the filter; write the param on dropdown change viahistory.replaceState; URL takes precedence overlocalStorage(kept as fallback for bare/).templates/web/board.html- add a hidden-by-default workspace breadcrumb anchor in the navbar before#board-name.static/web/js/whiteboard/app.js- inloadBoard()afterboard.get, resolve the workspace name viaworkspace.get, populate the breadcrumb, and point it + the back-arrow atWB_BASE + '/?workspace=' + workspace_id; suppress on/s/{token}pages.Implementation Plan
?workspace=on load (seedcurrentWorkspaceId; localStorage only when param absent). Files: home.html. Deps: none.replaceState(omit query for "All Workspaces"; skip the__new__sentinel). Files: home.html. Deps: 1..wb-navbar. Files: board.html. Deps: none.board.get+workspace.get, populate breadcrumb, set breadcrumb/back-arrow href to/?workspace={id}; guard against/s/{token}. Files: app.js. Deps: 4./,/board/{id}unchanged and/s/{token}shows no workspace UI. Deps: 1-5.Acceptance Criteria
/?workspace={id}selects that workspace and lists only its boards on first paint./(no param) shows the "All Workspaces" grouped view./board/{id}the top bar showsWorkspaceName / BoardName; the workspace name links to/?workspace={workspace_id}./?workspace={workspace_id}once resolved (and to/if resolution fails).?workspace={id}for a deleted workspace falls back to "All Workspaces" and clears the stale param./s/{token}shows no workspace breadcrumb and adds no workspace param..rschanges; no new routes; existing links still work.Notes
board.getalready returnsworkspace_idandworkspace.getalready exists, so the board page resolves both via existing/rpccalls.localStoragestays a fallback for bare/. UsereplaceState(notpushState) for filter changes. Set workspace/board names viatextContent(XSS-safe). Rebuild required (rust-embed + Askama compile at build time).Implemented and verified
Frontend-only, as specified — no backend, no new server routes.
Changes
templates/web/home.html?workspace={id}seeds the filter (URL is source of truth;localStorageis the fallback only when the param is absent).syncWorkspaceUrl()writes/?workspace={id}(or strips it for "All Workspaces") viahistory.replaceStateon dropdown change and inline workspace creation.templates/web/board.html#board-workspace-link+ separator) before the board name, and gave the back-arrow an id (#board-back-link).static/web/js/whiteboard/app.jsloadBoard(), afterboard.get, resolves the workspace viaworkspace.getand rendersWorkspaceName / BoardName, pointing both the breadcrumb and the back-arrow at/?workspace={workspace_id}. Suppressed on/s/{token}shared pages.Verification
Rust workspace lib tests (no regression):
cargo test --workspace --lib -> ok. 0 failed.Live checks against the running service (admin.sock):
GET /?workspace=1-> HTTP 200; served home containssyncWorkspaceUrl.app.jscontains_renderWorkspaceCrumb; served/board/{id}contains the breadcrumb element.board.list-> board 79,board.get->workspace_id=1,workspace.get-> name "My Workspace". So/board/79renders "My Workspace / gggggdddddd" linking to/?workspace=1.Deployed locally (release build, installed, service restarted).
Manual check needed (no browser automation here)
Open home, pick a workspace -> the URL gains
?workspace={id}; reload/share reproduces the filter. Open a board -> top bar showsWorkspace / Board; the workspace name and back-arrow return to the filtered home. Open an/s/{token}link -> no workspace breadcrumb.