Skip to content

Rename projects from the Projects page - #1125

Open
time-attack wants to merge 2 commits into
yc-software:mainfrom
time-attack:fix/project-detail-rename
Open

time-attack wants to merge 2 commits into
yc-software:mainfrom
time-attack:fix/project-detail-rename

Conversation

@time-attack

Copy link
Copy Markdown
Collaborator

Summary

Adds an owner-only inline rename affordance to the project title on the Projects detail page.

Fixes #321, reported by @ianTPE.

  • Reuses the existing renameProject action (existing PATCH /v1/projects/:id, existing owner-checked authorization) — no new backend, no new capability.
  • Small inline editor next to the project title, subtle pencil affordance for the owner only, matching the existing Slack-channel-linker editor pattern.
  • Keyboard: Enter saves, Escape cancels. Blank names show a validation error and stay in edit mode; unchanged names exit quietly. A busy guard disables the controls during a save, and a failed save preserves the typed draft with an error instead of discarding it silently.
  • Navigation-race safe: a slow save can't clobber a different project's draft/error after navigating away, or a freshly reopened draft on the same project.
  • Focus is restored predictably after every exit path (including on a real async save/failure), and never stolen from elsewhere on the page.
  • Visible on touch as well as desktop hover/focus.

Verification

  • 1026 web-ui tests pass
  • Typecheck, ESLint, oxlint, and Prettier all clean
  • Independently reviewed before this PR was opened

Demo

Rendered locally against synthetic fixture data (no production data), verified via automated real-DOM tests plus manual screenshots taken outside this repo.

Sina Matian added 2 commits September 11, 2026 14:13
Adds a subtle rename affordance next to the project title on the Projects
detail page, owner-only. Reuses the existing renameProject PATCH action and
the same inline-editor pattern already used for the Slack channel linker
(contextsState fields, form with save/cancel, existing .project-icon-button
styling).

- Idle: plain title + pencil icon, hidden until hover/focus, owner-only
- Editing: prefilled/focused/selected input, Enter saves, Escape cancels
- Blank name -> inline validation error, no API call, stays in edit mode
- Unchanged name -> exits editing quietly, no API call
- Busy guard disables input/buttons while a save is in flight
- Failed save keeps the typed draft and shows an error instead of silently
  discarding it, unlike the existing sidebar rename path
- Successful rename updates the shared contextsState.list project record,
  so the sidebar's existing project name lookups pick it up automatically

Adds test/project-title-rename.test.ts: a real-DOM test (same
JSDOM + vite.ssrLoadModule pattern as test/project-interactions.test.ts)
covering owner vs non-owner affordance, save/cancel/blank/unchanged/busy/
error/success, and propagation to the shared project list.

1022/1022 web-ui tests pass, typecheck/eslint/oxlint/prettier all clean.

Screenshots of the working UI (idle affordance, inline editing,
successful rename, and a bonus failed-save-preserves-draft state) were
shown to Sina and issue yc-software#321's rename-placement approach was approved.
Not pushed; no PR yet, pending a fresh independent review pass.
…project rename

Independent review of 0f346a9 found three blocking, cheap-to-fix issues:

1. Race: commitProjectTitleRename only checked contextsResetSeq, but
   selectContext (switching projects) resets the title-edit fields without
   bumping that sequence. A slow save on project A that resolves after the
   user has navigated to B (and started a B draft) could close B's editor
   or write A's error under B. Fixed with a dedicated titleEditGen counter
   bumped on every reset/navigate/reopen, plus titleEditProjectId, checked
   together (not just resetSeq or selected scope) before a commit's result
   is allowed to touch state. Covers A -> B and A -> B -> A (a fresh draft
   reopened on the same project after an earlier request from that same
   project is still in flight).

2. The hover/focus-only reveal for the rename pencil used a selector broad
   enough (.project-detail-title .project-icon-button) to also match the
   Save/Cancel buttons rendered inside the same header while editing, so
   they could fade to invisible whenever the mouse wasn't literally over
   the title (focus-visible only lights up the one focused control, not
   its siblings) -- and were never visible at all on touch, which has no
   hover state. Scoped the opacity rule to .project-title-edit only, and
   added the existing @media (hover: none) { opacity: 1 } convention
   already used elsewhere in this file (message actions, chat/cron row
   actions) so the pencil is always visible on touch.

3. Focus wasn't restored after every exit path. Escape/Cancel/unchanged
   now work via the existing data-focus-key + replaceChildrenPreservingFocus
   mechanism (added the same key to the pencil, input, Save, and Cancel so
   whichever one was active before a redraw is found again after it, and
   blank-submit correctly lands back in the input instead of a
   now-absent pencil). The busy-disabled inputs on a real save break that
   generic mechanism (a disabled element can't hold focus, so the
   pre-await render already drops focus to <body>), so added a small
   explicit restore after a real commit settles -- but only when focus is
   still sitting on <body>, so a slow save can never steal focus back from
   something the user has since focused elsewhere on the page.

Also added a cheap ownership recheck: if a background refresh finds the
signed-in user is no longer the project's owner while its title editor is
open, the editor now hides/cancels itself on both the render-time owner
gate and the state reset, instead of leaving a dual state a non-owner
could not actually save (server-side ownership enforcement is unchanged).

Left the .project-title-input / .session-rename-input CSS declarations
unshared -- reviewed, but they aren't actually identical (different
padding/font-size/weight) and forcing an abstraction across the old
sidebar rename input wasn't worth it for a few shared lines.

New test/project-title-rename-review-fixes.test.ts (4 more real-DOM tests,
same JSDOM + vite.ssrLoadModule pattern as the existing rename/interaction
tests): the A/B and A/B/A race guard, focus restoration across every exit
path plus the no-steal-focus case, the ownership-recheck-on-refresh
cancel, and a structural check that Save/Cancel don't carry the
opacity-hiding class. A real touch/no-hover screenshot was attempted for
the CSS fix but this sandbox has no CDP Emulation.setEmulatedMedia and no
way to create a hasTouch browser context, so that fix is covered by the
structural DOM test plus the source diff instead.

1026/1026 web-ui tests pass (1022 prior + 4 new), typecheck/eslint/oxlint/
prettier/git diff --check all clean. Desktop screenshots already shown and
approved are visually unaffected by this round (no layout/appearance
change), so they were not recaptured.

Independent review requested before PR; still not pushed, no PR yet, pending the next review pass.
@time-attack

Copy link
Copy Markdown
Collaborator Author

Actual UI preview

Rendered from this branch’s real QM UI with synthetic local data, cropped to the project header. The images show the owner’s rename affordance, inline editing, and the saved result. No production data was used.
rename-1-pencil
rename-2-editing
rename-3-saved

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.

No way to rename a project from the Projects page

1 participant