Skip to content

feat(staged): show short repo names on the project grid and kanban board - #1071

Merged
matt2e merged 1 commit into
mainfrom
kanban-and-project-grid-should-show-short-names
Nov 14, 2026
Merged

matt2e merged 1 commit into
mainfrom
kanban-and-project-grid-should-show-short-names

Conversation

@matt2e

@matt2e matt2e commented Nov 14, 2026

Copy link
Copy Markdown
Contributor

Summary

The project grid's cards, the board's cards and the filter chips above both named repos by their full path (block/builderbot, block/builderbot/apps/staged), while the sidebar already showed each repo's badge short name. They now show the same short name, with the full path kept as the tooltip.

  • New src/lib/shared/repoShortLabel.ts: repoShortLabel() returns the short name and hue of a repo's badge (the rule the sidebar already drew) and repoFullName() returns the full path for tooltips. No new formatting rule is introduced.
  • ProjectCardLabels.svelte (grid and board cards): a badged repo shows its short name in its hue.
  • ProjectFilterChips.svelte (chip bar above the grid and board): a repo chip shows its short name, as the sidebar's Filter menu does.
  • ProjectRowContent.svelte and SidebarFilterMenu.svelte: no visible change, they now call the shared helper instead of looking the badge up inline.

Behaviour notes

  • A badge belongs to one (repo, subpath) pair, so a monorepo subpath has its own short name ("staged" beside "bb").
  • The backend keeps short names unique across all badges, so the frontend adds no disambiguation.
  • A repo with no badge (not loaded yet, or a fork-backed repo whose filter key is the head repo) keeps its full path, matching the sidebar Filter menu's existing fallback.
  • Card repo order, filter keys, chip order, search matching and badge lookups are unchanged.

Not changed

Other surfaces still show full paths via RepoLabel (RepoCard, BranchCardHeaderInfo, DiffModal, ProjectTransferRepoRow, RepoConfigForm, RepoSearchInput, SuggestedRepos, HashtagInput, NewSessionModal, ActionsSettingsPanel); these can be handled separately if wanted.

Tests

Unit tests for the helper, plus card-label and filter-chip component tests covering short names, subpaths, tooltips, the no-badge fallback and that a chip still filters on the full repo and subpath.

🤖 Generated with Claude Code

The project grid's cards, the board's cards and the filter chips above
both named repos by their full path (`block/builderbot`,
`block/builderbot/apps/staged`), while the sidebar already showed each
repo's badge short name. They now show the same short name.

Where the short-name logic lives:
- New `src/lib/shared/repoShortLabel.ts`. `repoShortLabel()` returns the
  short name and hue of the repo's badge, the rule the sidebar already
  drew, and `repoFullName()` returns the full path for tooltips. No new
  formatting rule is introduced.

Components changed:
- `ProjectCardLabels.svelte` (grid cards and board cards): a badged repo
  shows its short name in its hue.
- `ProjectFilterChips.svelte` (chip bar above the grid and the board): a
  repo chip shows its short name, as the sidebar's Filter menu does.
- `ProjectRowContent.svelte` and `SidebarFilterMenu.svelte` (sidebar rows
  and Filter menu): no visible change, they now call the shared helper
  instead of looking the badge up inline.

How subpaths and collisions render:
- A badge belongs to one (repo, subpath) pair, so a monorepo subpath has
  its own short name: `block/builderbot` + `apps/staged` reads "staged",
  beside "bb" for the repo itself.
- The backend keeps short names unique across all badges (a second
  "goose" is stored as "goose2", and a rename onto a taken name is
  refused), so two repos never share a label and the frontend adds no
  disambiguation.
- A repo with no badge has no short name and keeps its full path. That
  covers a badge that has not loaded yet and a fork-backed repo in the
  filter chips, whose filter key is the head repo. This matches the
  sidebar Filter menu's existing fallback.

Display label only:
- The full path stays as the tooltip (`title`) on every shortened label;
  a fork-backed card label keeps the fork's path there, as before.
- Card repo order (by full path), filter keys, filter chip order, search
  matching and badge lookups are unchanged.

Tests: unit tests for the helper, and the card-label and filter-chip
component tests now cover short names, subpaths, tooltips, the no-badge
fallback and that a chip still filters on the full repo and subpath.

Left alone, still showing full repo paths (via `RepoLabel`), to handle
separately if wanted: `RepoCard` (the home "Repos" row, the sidebar's
pinned repos and the all-repos view), `BranchCardHeaderInfo`,
`DiffModal`, `ProjectTransferRepoRow`, `RepoConfigForm`,
`RepoSearchInput`, `SuggestedRepos`, `HashtagInput`, `NewSessionModal`
and `ActionsSettingsPanel`. `repoBadgeShortName` in
`projectSearchContext.ts` (search matching and the command palette) reads
the same badge field but was not rerouted through the new helper.

Signed-off-by: Matt Toohey <contact@matttoohey.com>
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 14, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

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

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-14T01:03:23.607324Z ece2d96 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.

@matt2e
matt2e merged commit 485b576 into main Nov 14, 2026
8 checks passed
@matt2e
matt2e deleted the kanban-and-project-grid-should-show-short-names branch November 14, 2026 01:06
matt2e added a commit that referenced this pull request Nov 14, 2026
CI on PR #1069 failed one test at e872926, deterministically:
`web_server::blocking_guard_tests::synchronous_commands_touch_only_in_process_state`
rejected `rename_project`. Main added that command in 6a8fe91 (#1044)
as a plain `fn`, after this branch's Fix A commits, so it ran on the main
thread and froze the window for its store write, its read and any wait
on the store lock. The guard is right, so the command is converted, not
allowlisted.

The branch was first rebased onto origin/main 485b576 (#1071), with no
conflicts; the nine commits are unchanged by range-diff. On that tree the
four guard tests were run before the fix: the one above fails, for
`rename_project` only, and the other three pass. Main brought no other
plain `fn` command or dispatch arm the guards object to.

Converted

- `rename_project` is now an `async fn` that only looks up the store
  handle and hands its whole body, `rename_project_impl`, to
  `tauri::async_runtime::spawn_blocking`, the pattern of d99d9f7. The
  body is unchanged.
- It is added to `OFFLOADED_COMMANDS`, so the command guard and the arm
  guard hold it to the handoff-only rule.
- Its web `dispatch` arm called `rename_project_impl` inline on a runtime
  worker. It now goes through `blocking_json`, with the same result and
  the same error text.

Ordering: the audit

The command has one frontend call site: the `renameProject` wrapper,
called by `handleRenameProject` in ProjectHome, which ProjectSection's
inline title editor awaits and which then paints the name into
projectsData with `projectRenamed`.

- Successive renames of one project. The editor holds
  `renameProjectSaving` and disables its input, so one mounted editor
  sends one rename at a time. The flag is component state, though.
  Opening another project and coming back while a rename is still out
  mounts a fresh editor, which can send a second. As a synchronous
  command the first froze the window, so that could not happen. On the
  pool the two wait on the store lock together and the older name can
  land last and stay. Exposed, and only when a rename is stuck for
  seconds.
- A read over the local paint. `projectRenamed` writes the name into the
  project list. A `list_projects` read sent before the rename landed may
  have read the store before it, and nothing refused it, so it put the
  old name back until the next list arrived. The usual case is a reload
  already in flight: the rename's own project-changed reload queues
  behind it and bumps no generation until it has been applied. Exposed,
  as a flicker. `list_projects` left the main thread in 8f6db6e, so
  this came with the rebase onto #1044 and not with this conversion.
- Another ordered write for the same project. There is none: no
  `writeOrder` key is per project. The tag writes for a project go to
  other tables and commute with the name.

Ordering: the fixes

- `renameProject` sends through `writeOrder` under `project:<id>`, so a
  project's rename waits for the one sent before it and for nothing
  else. Only the command is ordered; the cache invalidation after it is
  not.
- `projectRenamed` records a rename that really changes a name as a local
  write (`project-writes`). The two places that apply a project list,
  the reload and `refreshProject`, go through a new `admitProjectList`.
  A list sent before the rename is set aside, not applied, and the list
  is read again in the background, since it may carry some other change.
  The checks run in the order 8c519c1 settled on: refused, then set
  aside, then recorded, so a list that is set aside leaves no mark.

Residual risk

- A rename still in flight when its project is deleted. The delete is
  not sent behind the rename's key, so that a stuck rename cannot hold a
  delete back. If the delete's row removal won the store lock from a
  rename that was already waiting, the rename would fail on the missing
  project and leave its pinned error toast. The delete confirms in a
  dialog and takes the lock at least twice before it removes the row, so
  the rename lands first in practice.
- A rename that never settles holds back later renames of that project.
  As a synchronous command it held back every command.
- The order is per window, as for every other key.
- `projectCreated` paints a new project into the same list without a
  mark. A list read sent before the create can drop the project until
  the next reload. Not new and not changed here.

Verified

- Pre-fix, on the rebased tree, with the pinned 1.99.1 toolchain:
  `cargo test --lib blocking_guard_tests` gives 3 passed, 1 failed, with
  the CI message.
- `cargo fmt --check` passes.
- Prettier passes on the four frontend files changed.
- The five new frontend tests pass: one for the wrapper order, three for
  the list guard, and one pinning that a rename that changes no name
  marks nothing. With the list guard disabled two of them fail; with the
  rename key made unique per call the wrapper test fails.

Not run, left to CI as asked

- The guard tests after the fix, and the rest of the Rust test suite.
  That the converted command and arm satisfy the guards was checked by
  reading the guards' rules only.
- The full vitest suite.
- `cargo clippy --workspace -- -D warnings` and `pnpm check` had not
  finished when this commit was pushed.
- The app itself. Neither race was reproduced live.

Signed-off-by: Matt Toohey <contact@matttoohey.com>
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
matt2e added a commit that referenced this pull request Nov 14, 2026
…commands

CI on PR #1069 failed one test at e872926, deterministically:
`web_server::blocking_guard_tests::synchronous_commands_touch_only_in_process_state`
rejected `rename_project`. Main added that command in 6a8fe91 (#1044)
as a plain `fn`, after this branch's Fix A commits, so it ran on the main
thread and froze the window for its store write, its read and any wait
on the store lock. The guard is right, so the command is converted, not
allowlisted.

The branch was first rebased onto origin/main 485b576 (#1071), with no
conflicts; the nine commits are unchanged by range-diff. On that tree the
four guard tests were run before the fix: the one above fails, for
`rename_project` only, and the other three pass.

Main then moved again while CI ran the first push of this fix (c036996).
22c8335 (#1072, `stagedctl project open`) added three plain `fn`
commands, and the same guard rejected them as unlisted. The branch is
rebased onto 22c8335, again with no conflicts, and they are handled
below.

Converted

- `rename_project` is now an `async fn` that only looks up the store
  handle and hands its whole body, `rename_project_impl`, to
  `tauri::async_runtime::spawn_blocking`, the pattern of d99d9f7. The
  body is unchanged.
- It is added to `OFFLOADED_COMMANDS`, so the command guard and the arm
  guard hold it to the handoff-only rule.
- Its web `dispatch` arm called `rename_project_impl` inline on a runtime
  worker. It now goes through `blocking_json`, with the same result and
  the same error text.

Listed as synchronous

The guard's scan found nothing that blocks in #1072's three commands,
but it does not follow method calls, so each was read before it went
into `SYNCHRONOUS_COMMANDS`:

- `list_pending_project_opens` and `answer_project_open` read and answer
  a window's parked request: a map behind its own mutex, held for one
  lookup, and a oneshot send.
- `note_window_revealed` records the window in a set behind its own
  mutex and, if a raise is owed, shows and focuses the native window,
  sets the PR poll focus hint and records the window as used. The window
  calls belong on the main thread and the rest is in memory.

None reaches the store, a child process or the filesystem. They are not
converted, so their ordering is unchanged and they needed no frontend
audit.

Ordering: the audit

The command has one frontend call site: the `renameProject` wrapper,
called by `handleRenameProject` in ProjectHome, which ProjectSection's
inline title editor awaits and which then paints the name into
projectsData with `projectRenamed`.

- Successive renames of one project. The editor holds
  `renameProjectSaving` and disables its input, so one mounted editor
  sends one rename at a time. The flag is component state, though.
  Opening another project and coming back while a rename is still out
  mounts a fresh editor, which can send a second. As a synchronous
  command the first froze the window, so that could not happen. On the
  pool the two wait on the store lock together and the older name can
  land last and stay. Exposed, and only when a rename is stuck for
  seconds.
- A read over the local paint. `projectRenamed` writes the name into the
  project list. A `list_projects` read sent before the rename landed may
  have read the store before it, and nothing refused it, so it put the
  old name back until the next list arrived. The usual case is a reload
  already in flight: the rename's own project-changed reload queues
  behind it and bumps no generation until it has been applied. Exposed,
  as a flicker. `list_projects` left the main thread in 8f6db6e, so
  this came with the rebase onto #1044 and not with this conversion.
- Another ordered write for the same project. There is none: no
  `writeOrder` key is per project. The tag writes for a project go to
  other tables and commute with the name.

Ordering: the fixes

- `renameProject` sends through `writeOrder` under `project:<id>`, so a
  project's rename waits for the one sent before it and for nothing
  else. Only the command is ordered; the cache invalidation after it is
  not.
- `projectRenamed` records a rename that really changes a name as a local
  write (`project-writes`). The two places that apply a project list,
  the reload and `refreshProject`, go through a new `admitProjectList`.
  A list sent before the rename is set aside, not applied, and the list
  is read again in the background, since it may carry some other change.
  The checks run in the order 8c519c1 settled on: refused, then set
  aside, then recorded, so a list that is set aside leaves no mark.

Residual risk

- A rename still in flight when its project is deleted. The delete is
  not sent behind the rename's key, so that a stuck rename cannot hold a
  delete back. If the delete's row removal won the store lock from a
  rename that was already waiting, the rename would fail on the missing
  project and leave its pinned error toast. The delete confirms in a
  dialog and takes the lock at least twice before it removes the row, so
  the rename lands first in practice.
- A rename that never settles holds back later renames of that project.
  As a synchronous command it held back every command.
- The order is per window, as for every other key.
- `projectCreated` paints a new project into the same list without a
  mark. A list read sent before the create can drop the project until
  the next reload. Not new and not changed here.

Verified

- Pre-fix, on the tree rebased onto 485b576, with the pinned 1.99.1
  toolchain: `cargo test --lib blocking_guard_tests` gives 3 passed, 1
  failed, with the CI message.
- CI run 43540379914 on c036996, which is this commit without the three
  list entries, merged with 22c8335. Rust lint and Frontend (prettier,
  svelte-check, tsc, vitest) pass. Rust test: 2271 passed, 1 failed. The
  failure is the same guard, now naming only #1072's three commands, so
  `rename_project` passes all four guards there.
- Locally on c036996: `cargo fmt --check`,
  `cargo clippy --workspace -- -D warnings`, `pnpm check` (0 errors) and
  prettier on the changed frontend files pass.
- The five new frontend tests pass: one for the wrapper order, three for
  the list guard, and one pinning that a rename that changes no name
  marks nothing. With the list guard disabled two of them fail; with the
  rename key made unique per call the wrapper test fails. The two test
  files they are in pass in full (151 tests).
- After the second rebase and the list entries: `cargo fmt --check`.

Not run, left to CI as asked

- Any test after the second rebase and the three list entries. The guard
  has not been seen to pass with them; they were added from its failure
  message and from reading the commands.
- The Rust test suite and the full vitest suite locally.
- The app itself. Neither race was reproduced live.

Signed-off-by: Matt Toohey <contact@matttoohey.com>
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
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