Skip to content

A way back on every page, plus two fixes it surfaced - #408

Merged
Deodat-Lawson merged 7 commits into
mainfrom
claude/unified-back-navigation
Sep 20, 2026
Merged

Deodat-Lawson merged 7 commits into
mainfrom
claude/unified-back-navigation

Conversation

@Deodat-Lawson

@Deodat-Lawson Deodat-Lawson commented Sep 19, 2026

Copy link
Copy Markdown
Owner

Summary

Three independent commits, each revertable on its own:

  • A unified way back. /employer/settings was a dead end you left by editing the URL — and an audit found a back affordance on 2 screens out of 29. A 38px bar in DriftShell now gives every route under /employer the same control in the same place.
  • Notes removed from Studio. Notes are an annotation on a document, not a destination. The standalone Notebook — tile, ⌘K entry, Mod+Shift+N, ?feature=notes route, pane — is gone. The Notes tab in DocumentViewer is untouched.
  • Mindmap templates no longer open at 5%. The editor had the same one-shot framing bug the preview had; both now share one hook.

Related

Follows #398, which fixed the preview half of the framing bug.

Checklist

  • pnpm check passes (lint + typecheck)
  • pnpm --filter @launchstack/web test passes
  • Changeset added (if packages/core/ changed — pnpm changeset)
  • New env vars documented in .env.example and apps/web/src/env.ts
  • UI changes exercised in a browser (not just a green build)
  • Docs updated if behavior changed

On the unticked boxes: the full @launchstack/web suite was not run end to end — 647 tests across the 45 affected suites (_mindmap, _workspace, back-target) were. No changeset (packages/core/ untouched); no env vars added.

pnpm check needs more than the default 4 GB heap here — eslint . aborts with an OOM at 4 GB and passes clean under NODE_OPTIONS=--max-old-space-size=8192. The same limit now breaks make up (the in-Docker Next.js build OOMs on current main), so make up-fast is the working path. Unrelated to this change, but worth knowing.

Testing

Built and deployed to the local stack, then verified in the browser rather than reasoning about it.

Back bar — all 16 reachable routes serve data-testid="back-bar". Measured on both kinds of page:

Page Bar Behaviour
/employer/settings#company ← Studio / Launchstack / Settings
/employer/documents (immersive) ← Workspaces / Documents bar 38 + shell 1316 = 1354 viewport, no page scroll
/employer/tools/prospects/companies (immersive) ← Prospects section-aware, not all the way to Studio

Notes — no "Notebook" in the served Studio HTML, no feature=notes route, nothing in the ⌘K palette.

Template zoom — created a SWOT mindmap via POST /api/mindmaps and opened the editor: stage 1230×1234, viewBox 915×918, zoom 134%. Previously 5%.

647 tests pass across 45 suites.

Notes for reviewers

Why the bar is in flow rather than floating. Nearly every screen here already owns its top-left corner — the Studio rail's logo, the Prospects rail, the settings rail. I checked settings visually first; a fixed pill would have landed on top of one of those on most pages. A bar above the page cannot collide with anything.

Why it goes up rather than back. backTarget.ts derives the destination from the path, not from history, because history is wrong in two ordinary cases: a deep link opened in a fresh tab has nothing behind it, and a page reached sideways sends you somewhere unrelated. A computed parent is the same answer every time from the same page. It names the destination rather than saying "Back", and a section's landing page is the parent for what is under it but never for itself — unit-tested, including a "never points at itself" property. Even the Studio has somewhere up (the workspace picker), so no page is a dead end.

The riskiest hunk is the viewport math. Two immersive shells claim 100dvh and now subtract the bar through --drift-backbar-h. DriftShell.module.css carries a long comment about why that chain is delicate, so I verified the arithmetic in the browser rather than trusting it.

Side benefit: the bar renders page breadcrumbs, which finally gives BreadcrumbContext a consumer — it has been collecting trails since the topbar that displayed them was removed.

NotebookPane.tsx (420 lines) is deleted, not left unreachable, since the pane was its only caller. Its shared pieces — NoteCard, NoteDraftEditor, BacklinksPanel — stay; the document panel uses them. One git revert away if that is the wrong call.

On the framing hook: refitting on every resize alone would be worse than the bug, yanking the board back under someone who had just panned. Telling "the stage is still settling" apart from "the author moved it" needs no state in the store — only the viewport our own framing produced, and a check that the live one still matches it.

🤖 Generated with Claude Code


Note

Medium Risk
Changes global employer chrome viewport math and adds workspace-scoped destructive deletes across several history tables; chat API/UI metering fields also shift what clients display.

Overview
Adds a shell-level back bar on every /employer route: path-derived parents via backTargetFor (Studio → section landings → Workspaces), breadcrumb trail from BreadcrumbContext, and immersive layouts that subtract --drift-backbar-h so tools and Studio still fit the viewport.

Studio / workspace polish: removes the standalone Notebook feature (pane, shortcuts, ?feature=notes); simplifies the source rail Add control (drops the split “new mindmap” menu). History rail can delete non-chat pipeline runs (confirm → optimistic UI → DELETE /api/workspace/history/<kind>/<refId> with per-vertical remove loaders).

Mindmaps: shared useAutoFit refits while the stage resizes until the user pans/zooms, fixing tiny zoom on template open and preview panels.

Ask / citations: document Q&A responses now expose tokenUsage; the chat UI shows tokens vs chunks separately and renders quoted questions as blockquotes. Citation copy/UI stops showing page numbers (indexing didn’t have real pages).

Reviewed by Cursor Bugbot for commit 41f087c. Bugbot is set up for automated code reviews on this repo. Configure here.

Deodat-Lawson and others added 3 commits September 17, 2026 22:29
/employer/settings was a dead end — you left it by editing the URL. So were
employees, statistics, metadata, upload, contact, agent-sessions and five of
the six tools: an audit found a back affordance on exactly two screens out of
twenty-nine. The two that had one had each invented it separately, a "Back to
all artifacts" link mid-page and a "Back to Studio" pinned to the bottom of a
rail, which is the other half of the problem — even where it existed you had to
go and find it.

It now lives in DriftShell, so every route under /employer gets the same
control in the same place.

**In flow, not floating.** Nearly every screen here already owns its top-left
corner — the Studio rail's logo, the Prospects rail, the settings rail — so a
fixed pill would have landed on top of one of them on most pages. A 38px bar
above the page cannot collide with anything.

**It goes up, not back.** `backTarget.ts` derives the destination from the
path rather than from history, because history is wrong in two ordinary cases:
a deep link opened in a fresh tab has nothing behind it, and a page reached
sideways would send someone somewhere unrelated. A computed parent is the same
answer every time from the same page, which is what lets you stop thinking
about it. It names the destination ("Studio", "Prospects", "Companies") rather
than saying "Back", and a section's landing page is the parent for what is
under it but never for itself.

Even the Studio has somewhere up — the workspace picker — so no page is a dead
end.

The bar also renders whatever crumbs a page publishes, which finally gives
BreadcrumbContext a consumer: it has been collecting trails since the topbar
that displayed them was removed.

The two immersive shells claim `100dvh`, so they now subtract the bar's height
through `--drift-backbar-h`. Verified in the browser rather than reasoned
about: on the workspace, bar 38 + shell 1316 = 1354 viewport exactly, with no
page scroll.

Checked all 16 reachable routes serve the control, and the routing table is
unit-tested including the never-points-at-itself property.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Notes are an annotation on a document, not a destination. The Studio carried
them as a top-level feature anyway: a tile, a ⌘K entry with its own
Mod+Shift+N binding, a `?feature=notes` route and a pane wrapping a
cross-document scratchpad.

All of that goes. What stays is the thing notes actually are — the Notes tab
in DocumentViewer, backed by DocumentNotesPanel, which is untouched.

`NotebookPane` (420 lines) had no other caller once the pane went, so it is
deleted rather than left unreachable. Its shared pieces — NoteCard,
NoteDraftEditor, BacklinksPanel — stay; the document panel uses them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Opening a template rendered the whole board at 5%. Same bug the preview had,
written a second time: frame once on the first measurement over 40px, latch a
ref, never look again. Neither surface is at its final size when it mounts —
the preview opens while its panel animates, the editor opens with both side
panels sliding in — so the fit is computed against a half-width stage and then
kept forever.

The preview's fix is now a hook both use. Refitting on every resize alone would
be worse, yanking the board back under someone who had just panned; telling the
two apart needs no state in the store, only the viewport our own framing
produced and a check that the live one still matches it.

`frame()` also re-arms tracking, so everything that means "frame this now" —
the Fit control, a page change, entering presentation — goes through it and
starts following along again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Sep 19, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
launch-stack Error Error Sep 19, 2026 11:18pm UTC
1 Skipped Deployment
Project Deployment Actions Updated
pdr-ai-v2 Ignored Ignored Sep 19, 2026 11:18pm UTC

@cursor

cursor Bot commented Sep 19, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_e316c0c4-3441-496d-b2be-f71b832353b0)

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 19, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-19T21:30:01.315865Z 822f670 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 822f670029

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +33 to +35
const pathname = usePathname();
const { crumbs } = useBreadcrumbs();
const target = backTargetFor(pathname ?? "");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Account for query-backed editor states

usePathname() omits the query string, but the Studio represents source preview and mindmap editing as /employer/documents?source=<id>&edit=1 in WorkspaceShell. Consequently, the visible bar in a mindmap editor treats it as the Studio root and links to Workspaces, skipping the intended editor → preview → library hierarchy and the editor's explicit save-before-return handler. Include the search-parameter-backed state when deriving this target.

Useful? React with 👍 / 👎.

Comment on lines 114 to 117
const FEATURE_IDS = new Set([
"draft",
"rewrite",
"notes",
"workflows",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep freeform notes reachable after saving

Removing notes from the reachable feature set and deleting its only pane leaves no UI that lists freeform notes, while the chat context-menu action in WorkspaceShell still posts unanchored notes to /api/notes and reports “Saved to your notebook.” Those newly created notes—and users' existing notebook notes—cannot then be opened, edited, or deleted through the application. Either retain a reachable notebook surface or remove/redirect the save action and migrate existing freeform notes.

Useful? React with 👍 / 👎.

Deodat-Lawson and others added 2 commits September 19, 2026 17:31
One conflict, in WorkspaceShell's FEATURE_ROUTES: main moved Prospects under
the new Growth app while this branch removed the `notes` route. Took both —
Growth's paths, without notes.

The move also stranded this branch's back-target table, which still pointed at
`/employer/tools/prospects/*`. Retargeted onto Growth, with Brand and Growth
itself as sections of their own, and the old Prospects path kept as an entry
that forwards to where it lives now rather than to a route that no longer
exists.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The "10 tokens" under an answer was never a token count. WorkspaceShell set
`tokens: data.chunksAnalyzed`, and the route sets `chunksAnalyzed:
documents.length` — so the figure was the number of retrieved chunks, wearing
a tokens label. It read low because a dozen chunks is a plausible-looking
number next to a real count in the thousands.

The real numbers were already there. The route calls `normalizeTokenUsage()`,
logs the prompt/completion split, and debits credits against it — then dropped
it instead of returning it. It now travels in `tokenUsage`, which is the same
figure metering bills, so what a turn says it cost and what it actually cost
cannot drift.

Both numbers are worth seeing and they are different things, so the line now
reads "1,432 tokens · 10 chunks" — what the model billed, and what it read —
with the prompt/completion split on hover.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@cursor

cursor Bot commented Sep 19, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_b338b42a-fa60-414c-af50-d16f64b9d887)

"Explain this", "Summarise this" and the composer's quote action all write a
Markdown blockquote into the message. The chat renders a user turn as plain
text in a div with `white-space: normal` — so the `>` markers showed up
literally and every line break collapsed. A two-line quote arrived as

    Explain this passage from "Annual Report": > Revenue grew 14% year over
    year, > driven mainly by enterprise renewals.

one run-on line in which the question and the thing being quoted are
indistinguishable.

The markers are now read back out. `parseQuotedMessage` splits a message into
what was asked, the passage, and anything typed after it; the turn draws the
passage with a left rule in the secondary ink, and `pre-wrap` keeps line breaks
in every part — which also fixes plain multi-line questions, which collapsed
the same way.

Only a contiguous run of `>` lines counts, so a chevron later in a question
does not silently swallow the rest of it. Six cases pinned, including blank
lines inside a passage and an empty blockquote.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@cursor

cursor Bot commented Sep 19, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_b003c7ef-06f6-4078-90f8-fc26b96a80b4)

…ow deleting a run

**The add-button caret.** The `+` was a split button whose second half opened a
menu of "New mindmap" and "Add a source". Both already live in the Add-source
dialog's Create group — one click further in through the button's own primary
half — and mindmap is in the ⌘K palette besides. The plain `+` stays; the
now-unused `onNewMindmap` prop goes with it.

**The page badge.** `p. 1` beside a citation was not a location. Indexing
writes `page_number: 1` for every chunk of every document — all 82 rows in
this database, including all 33 of a 40-page PDF — so it read "p. 1" whether a
passage came from the first page or the last. A citation that looks precise
and is wrong is worse than one that says nothing, so the badge and the page in
the copied citation string are both gone.

That is a removal, not a fix: making it true means recording real pages in the
converter and chunker and reindexing every existing document. The page still
travels in the reference, and the tests pin its absence so restoring it is a
deliberate act.

**Deleting a run.** Every non-chat row was permanent — the menu offered "Open"
and nothing else, and a vertical with no surface yet offered an empty menu, so
those rows could never be removed at all.

Delete is now part of the loader contract, beside load. Each pipeline loader
deletes from its own table scoped by company, so the rail stays ignorant of
`distribution_runs` and `trend_search_jobs`; a kind with no safe delete omits
`remove` and its rows offer none, the way a kind with no `href` offers no
open. `DELETE /api/workspace/history/<kind>/<refId>` dispatches, and a row
belonging to another workspace answers 404 exactly as one that never existed,
so the endpoint cannot be used to probe for ids. Chat is refused there on
purpose: sessions are personal, not workspace-scoped, and have their own
delete with its own ownership rule.

The rail asks first — a run cannot be restored — and removes the row
optimistically, restoring it if the server disagrees, matching the chat delete
beside it.

Kept as one commit because WorkspaceShell carries both the caret removal and
the new delete handler; splitting by file would leave a commit that does not
compile.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@cursor

cursor Bot commented Sep 19, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_350bc030-f325-4de0-9434-73c2942f3eac)

@Deodat-Lawson
Deodat-Lawson merged commit 4d4e252 into main Sep 20, 2026
15 of 16 checks passed
@Deodat-Lawson
Deodat-Lawson deleted the claude/unified-back-navigation branch September 20, 2026 02:38
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