Skip to content

Edit the document title in place in the header - #343

Merged
MaggieAppleton merged 6 commits into
mainfrom
design/inline-document-title
Oct 8, 2026
Merged

MaggieAppleton merged 6 commits into
mainfrom
design/inline-document-title

Conversation

@MaggieAppleton

@MaggieAppleton MaggieAppleton commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

Why

New documents get random slug names ("eager-valley"), and the only way to name one was header chevron → Rename → modal. Nobody bothers, so the sidebar fills with names nobody chose.

What changed

  • The header title is editable in place, like a Linear issue title. Click it, press F2 (or Enter/Space while it's focused), or choose Rename from the chevron menu. Enter or blur commits, Escape reverts, and an empty or unchanged value does nothing.
  • Zero layout shift. The field keeps the resting title's box, size, and ink (no min-width), so the text and chevron don't move. Editing shows only a selected-grey wash, a hairline petrol ring, and the caret. The ring geometry lives in theme.css beside the other focus rules.
  • At rest the title is plain text. On hover it gets the selected wash, which is more visible on the ground than the old hover wash. The chevron is its own button ("Actions for …") and darkens to secondary ink on header hover.
  • New document: from the click until the title field mounts, printable keys and Enter are held (holdTyping in title-edit.ts) instead of landing on the page or the editor. The field then mounts focused with the slug selected (placeholder "Untitled"), replays the held text in place of the slug, and commits if Enter was pressed. Nothing typed early becomes a partial name or reaches the document body. Enter or Escape then moves the caret into the body. The hold skips other inputs and textareas, and stops if creation fails or you navigate away.
  • Rename is never a modal. Sidebar row Rename opens that document and its inline title. rename-document-dialog.tsx is deleted. The request is held in title-edit.ts (the workspace claims it in its own lazy module) (expires after 15 s) and also signalled with a window event when the document is already open.
  • Errors: a 409 reads "A document with this title already exists. Try a different title." Other errors keep the server message. Leaving a rejected title reverts it and closes the field, without retrying.
  • The document icon no longer shrinks on phones with long titles.
  • Archived documents and read-only viewers get plain text. The child breadcrumb header is unchanged.
  • DocumentRename gains an inline variant that reuses the same request and validation.
  • No overlap with Show document connection status in the document header #297: it doesn't touch Header.

Screenshots

Before: header at rest, and renaming needs a modal

before rest
before modal

After: rest / hover / editing (same box, so no shift)

after rest
after hover
after editing

After: right after New document (generated name selected)

after new document

After: duplicate title

after error

Phone: rest / editing with a long title

phone rest
phone editing

Testing

  • bun run types: pass.

  • cd apps/web && bun test src/: all pass. Under heavy machine load a few repo-walking tests time out at 5 s; they pass with --timeout 60000. navigation-chrome.test.ts covers the title button, the inline field, and the archived and viewer cases.

  • bun scripts/check-design-record.ts: pass. bun run ci: fails only on the pinned review hashes below.

  • Bundle budget: the container build passes (246,869 B raw / 78,772 B gzip, merged with current main). The typing hold adds about 130 B gzip to the initial chunk.

  • Checked in a real browser against a fake-GitHub server:

    • Under 4x CPU throttling, typing 16 characters plus Enter straight after clicking New document (0 ms and 10 ms delays, four runs) gave the full title every time, with nothing in the body.
    • Rest and editing boxes are identical (x 286, width 90.9, height 29.0), and the chevron x doesn't change.
    • Sidebar Rename of another document navigates there with the field focused and no dialog.
    • A 409 shows the new copy, and blur reverts with no second PATCH.
    • No horizontal overflow at 390px.
  • E2E in CI passes, with new tests:

    • typing 16 characters plus Enter straight after New document under 4x CPU throttling, at 0 ms and 10 ms delays
    • sidebar Rename opening another document inline
    • leaving a rejected title reverts without retrying
    • click, F2, Escape and blur in place

    The existing rename tests press Enter instead of clicking the removed Save button.

Needs a design-contract review

These files are pinned in scripts/design-contract/exceptions/dynamic-web.json:

  • apps/web/src/document-rename.tsx, new hash c9df5d5496e382136786e52f65a0d7a096baecd1d9e42ac860579b1b2d7ddd25. The existing className forwarding on the form variant is unchanged. The inline branch uses only static classes.
  • apps/web/src/navigation-shell.tsx, new hash d081007f84589d21b71a0584a54d6487f9fcb53b5a06937e8b5a1f25b958deb7. The rename dialog branch and its lazy import were removed, and Rename now navigates. No style or class logic changed.

🤖 Generated with Claude Code

@MaggieAppleton

Copy link
Copy Markdown
Collaborator Author

Review: Changes needed

Checked in a real browser (headless Chromium, fake-GitHub server) at 1440x900 and 390x844. Keyboard, blur, New document flow, two-user rename, long titles, and menu Rename all work. The items below are polish and one real input-loss bug.

Must fix

  1. Quick typing after New document is lost, and can leave a partial name. Sequence is route change, then body focused, then the title field focused (room-workspace.tsx claims in a useEffect). Typing "Quick" right after the URL changes produced a title of uick: the first characters went to body, the rest replaced the selected slug. Fix: claim during the first render (initial useState value from claimNewDocumentTitle(room)) so the field mounts and focuses with the workspace, and/or focus the title before the editor takes focus.
  2. Layout shift at rest to editing. Box/type match (same x, y, height 29px, 14.04px, 4px padding), but .document-title-input { min-width: 12ch } widens a short title: calm-stone goes 83px to 106px, so the chevron jumps right by 23px. Drop the min-width (or apply it to the rest button too).
  3. Error copy. The alert shows the server string verbatim: "a document with this title already exists". Please map it (409) in document-rename.tsx to sentence case, e.g. "A document with this title already exists. Try a different title." Also at 1440 the alert floats over the Chat panel header; fine, but it has no dismiss other than typing.
  4. Blur after a failed rename. Clicking away leaves the field open (focus elsewhere), the alert stays, and the blur re-fires the failing request. Suggest: on blur with an error, revert and close (or at least don't resubmit).

Should fix
5. Editing state is heavier than a Linear title. Rest has no box; editing has a white fill plus a 2px teal ring and the text darkens from tertiary to primary. Consider a 1px ring or the hover wash plus caret, keeping the text colour. The rest hover wash (--color-hover) is very faint on this background.
6. Phone. With a long title (rest and editing) the document icon squeezes to a dot: add flex-shrink: 0 to it. Under (pointer: coarse) the field becomes 16px (theme.css:858) while rest is ~13.4-14px, so text jumps on focus; acceptable for iOS zoom, but worth knowing. No horizontal overflow (scrollWidth = 390).
7. Sidebar Rename consistency. The PR body says it still opens the modal. Observed: for the document that is open, row Rename already focuses the inline title (no dialog); for any other document it opens the modal. Suggest navigating there and claiming an inline edit (reuse the claim from use-document-creation.ts) so Rename is never a modal. Update the PR body either way.
8. Chevron discoverability. It is a separate 22px button with tooltip "Document actions", aria "Actions for ...", and a hover wash. Reasonable, but it is faint (tertiary) now that the title no longer looks like a menu trigger. Optional: bump to secondary colour on header hover.

Notes (no action needed)

  • Two users renaming: no draft clobbering; last write wins, titles propagate to the other user's header and sidebar. Silent overwrite is acceptable for v1.
  • F2, Enter, Space on the button, Escape (focus returns to the title), Tab out, blur commit, and an empty or unchanged value all behave. Tab from a brand-new unchanged title returns focus to the title button, not the body; fine.
  • untitled Set in use-document-creation.ts is never cleared if the workspace never mounts (tiny leak); StrictMode double effects would consume the claim once (dev only).
  • Budget: local build passes; the container job fails the gzip budget by 11 B (documented; a headroom PR will absorb it). bun run ci fails only on the pinned review hash for document-rename.tsx, which is acceptable here.

Screenshots (local): .design-orchestrator/audit-2026-10-07/shots/review-343/.

@MaggieAppleton

Copy link
Copy Markdown
Collaborator Author

Thanks. All eight items, plus the minor one, are addressed in a9b6a10 and 6094614:

  1. Quick typing: the workspace now claims the edit in its initial useState, so the field mounts focused with the workspace. In the browser, typing right after the URL changed kept every character. New e2e: "typing straight after New document names it without losing a character".
  2. Layout shift: min-width is gone. Rest and editing boxes are identical (x 286, w 90.9, h 29.0), and the chevron doesn't move.
  3. 409 copy: now "A document with this title already exists. Try a different title." This also covers the e2e for item 4.
  4. Blur after a failure: the field reverts and closes with no second PATCH. New e2e: "leaving a rejected title reverts it without retrying".
  5. Lighter editing: same ink as rest, a selected-grey wash, a hairline petrol ring, and the caret. The ring geometry is in theme.css next to the other focus rules, which focus.test.ts requires. The rest hover now uses the selected wash, which shows up on the ground.
    • Note: the resting title is tertiary ink, not primary, so editing stays tertiary to keep zero change between states. If you want both primary, it's a one-line change.
  6. Phone: the document icon is shrink-0.
  7. Sidebar Rename: it never opens a modal now. It navigates to the document and opens the inline title through the same claim (title-edit.ts). If the document is already open, a window event triggers it. rename-document-dialog.tsx is deleted. New e2e: "sidebar Rename opens that document with its title ready to edit".
  8. Chevron: it darkens to secondary ink on header hover.
  • Minor: pending title-edit requests expire after 15 s.

CI: e2e and container pass. The container bundle is now 81,472 B gzip, under budget. bun run ci fails only on the pinned review hashes for document-rename.tsx and navigation-shell.tsx; both are listed in the PR body. Screenshots in the body are updated.

@MaggieAppleton

Copy link
Copy Markdown
Collaborator Author

Review: Changes needed (one item left)

Re-verified at 6094614 in a fresh worktree (headless Chromium, 1440x900 and 390x844, fake-GitHub on 8843).

Fixed

  • Zero rest to editing shift: trigger and field both x=286, y=10.5, h=29.05, 14.04px; chevron stays at x=360.9 in both states.
  • 409 copy: "A document with this title already exists. Try a different title."
  • Blur after a failed rename: field closes, title reverts, alert gone, 0 non-GET requests fired.
  • Editing state: selected wash, 1px ring, same ink as rest. Looks quiet now.
  • Sidebar Rename never opens a modal: for a non-open doc it navigates and focuses the inline field; for the open doc it focuses it directly; Escape returns focus to the title.
  • Chevron darkens on header hover.
  • Phone long title: icon stays 14px, no horizontal overflow (scrollWidth 390), ellipsis at rest.

Still broken: immediate typing after New document
Typing right after the URL changes still loses leading characters. 16 chars at 10ms delay gave CDEFGHIJKLMNOP, at 30ms BCDEFGHIJKLMNOP; document.activeElement is BODY for a window after the route changes, then the field mounts and selects its slug. Both runs happened on a heavily loaded machine, which widens the gap, but the window is structural. Suggest closing it rather than shrinking it: either buffer keystrokes while the claim is pending and replay them into the field, or focus a hidden/placeholder title input in the same commit as the route change. At minimum, make sure keys typed before the field is ready never become a partial name (the uick case from the first review).

gh pr checks 343 is reported separately by the reviewer.

@MaggieAppleton

Copy link
Copy Markdown
Collaborator Author

Fixed structurally in 61dad1f.

From the moment New document is clicked, holdTyping holds printable keys and Enter in a capture-phase listener, and prevents them from reaching the page. Nothing lands on body or in the editor. When the inline title mounts, it focuses, takes the held text in the same task (so no key slips between the hold and the field), replaces the selected slug with it, and commits if Enter was held. The hold:

  • skips other inputs and textareas
  • stops if creation fails or you navigate away

E2E: the test now uses Emulation.setCPUThrottlingRate at 4. It clicks New document, immediately types 16 characters plus Enter at 0 ms and at 10 ms delays, and asserts the full title and that the body doesn't contain the text. Locally, under the same throttling, four runs all gave the full 16-character title.

Known edge: at 0 ms with a held Enter, the commit can finish before the editor mounts, so focus falls back to the title button instead of the body.

CI: e2e and container pass (initial JS 246,869 B / 78,772 B gzip on the merge with main). bun run ci fails only on the two pinned review hashes. The document-rename.tsx hash is updated in the PR body to c9df5d54….

MaggieAppleton and others added 6 commits October 8, 2026 03:16
Clicking the header title, pressing F2, or choosing Rename from the
document menu now swaps the title for an inline field. Enter or blur
commits through the existing rename request; Escape reverts. A document
created from New document opens with its generated name selected, and
committing or dismissing it hands the caret to the document body.
Archived and read-only viewers keep plain text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Scope the editor lookup to the header's workspace, so an outgoing route
layer's editor can't take focus, and move it before the field unmounts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claim the title edit during the workspace's first render so a new
document's field is focused as soon as it mounts. Sidebar Rename now
opens the document and its inline title instead of a modal, which is
removed. Editing keeps the resting box and ink on a selected wash with a
hairline ring, a duplicate title gets sentence-case guidance, and leaving
a rejected title reverts it instead of retrying.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keys pressed while a new document is created and opened landed on the
page, so a fast typist lost the start of the name. Hold printable keys
and Enter from the click until the inline title mounts, then replay them
in place of the generated name, committing if Enter was pressed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaggieAppleton
MaggieAppleton force-pushed the design/inline-document-title branch from 61dad1f to 1390265 Compare October 8, 2026 02:16
@MaggieAppleton
MaggieAppleton merged commit 0656783 into main Oct 8, 2026
3 checks passed
@MaggieAppleton
MaggieAppleton deleted the design/inline-document-title branch October 8, 2026 02:25
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