Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions docs/REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -464,8 +464,10 @@ so old bookmarks keep working.
the shared `sortContainers` (active first, archived last, rank-then-name;
PROG-83) — the child lists on container pages. **Every other container/tag
list is deterministic too** (PROG-83): pickers and selects (create dialogs,
palette arc/move/tag pickers, palette container quick-jump, filter
dropdowns per PROG-66) list alphabetically by name, tag chips on action
palette tag picker, palette container quick-jump, filter
dropdowns per PROG-66) list alphabetically by name — except the palette
**move/arc pickers**, which follow this same outline order and hint each row
with its parent container (PROG-123) — tag chips on action
pages and board/Agenda cards sort alphabetically (shared `tagsByAction`,
`src/client/tags.ts`), and Archive groups sort by name. Board **actions**
keep pure `rank` order — never alphabetized. The scope
Expand Down Expand Up @@ -653,7 +655,10 @@ so old bookmarks keep working.
actions by key (retired alias keys included) or title and containers by
name, and lists commands (create action/workspace/focus/arc,
pickers for the current action). Picker modes are filterable lists; tag
toggles keep the palette open for multi-edit.
toggles keep the palette open for multi-edit. The move picker lists focuses
grouped by workspace in outline rank order with the parent workspace as the
row hint (a typed query matches it too); the arc picker lists the focus's
arcs in outline rank order, hinting the parent focus (PROG-123).
- **Create dialogs** — action and container creation; parents/containers
default from the current view (open container page, viewed action's
container, or active board filters). New actions default to **Todo** so
Expand Down
24 changes: 24 additions & 0 deletions docs/decisions/PROG-123.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
### PROG-123 — move/arc pickers follow outline order and name the parent

The palette's move (focus) and arc pickers listed alphabetically per PROG-83's
"pickers list alphabetically" rule, with a generic "Focus" hint. That rule
predates the owner curating a manual order on the Outline/Structure pages; a
picker that ignores it fights the order the owner set, and a bare focus name
is ambiguous once the same name exists under two workspaces (e.g. "Admin").

Superseding PROG-83 **for these two pickers only**:

- **Move picker** — focuses group under their workspace in structure order
(workspaces by `sortContainers`, focuses by `byRankThenName` within each),
and the row hint is the parent **workspace name** instead of the constant
"Focus". Since the workspace name is on screen, the filter query matches it
too — typing a workspace name lists that workspace's focuses.
- **Arc picker** — arcs sort `byRankThenName` instead of alphabetically, and
the hint names the parent **focus** (the level one up, per the action). All
rows share it — an arc picker is scoped to the action's focus — but it
confirms where the arc lives; "current" still wins on the current arc, and
"No arc" stays pinned first.

Every other picker (tags, create dialogs, filter dropdowns, quick-jump) keeps
PROG-83's alphabetical order — nothing else displays rank, so alpha remains
the scannable choice there.
36 changes: 26 additions & 10 deletions src/client/commands/CommandPalette.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useLocation } from "wouter";
import { ACTION_ESTIMATES, ACTION_PRIORITIES, ACTION_STATUSES } from "../../shared/constants";
import type { WireAction, SnapshotPayload } from "../../shared/types";
import { sortByName } from "../boardFilters";
import { byRankThenName, sortContainers } from "../containerReorder";
import { addDays, formatDueDate, relativeDue, todayISO } from "../dates";
import { PRIORITY_LABELS, STATUS_LABELS } from "../labels";
import {
Expand Down Expand Up @@ -216,10 +217,14 @@ function buildItems(
return items;
}
case "arc": {
// Alphabetical like every picker (PROG-83); "No arc" stays pinned first.
const focusArcs = sortByName(
ws.arcs.filter((a) => a.focusId === action.focusId && !a.archivedAt),
);
// Outline order, not alphabetical (PROG-123 supersedes PROG-83 here):
// arcs list in the manual rank set on the outline/structure pages, and
// each row's hint names the parent focus one level up. "No arc" stays
// pinned first.
const parentFocus = ws.focuses.find((p) => p.id === action.focusId);
const focusArcs = ws.arcs
.filter((a) => a.focusId === action.focusId && !a.archivedAt)
.sort(byRankThenName);
// "No arc" filters like any option, so a typed query can't leave it
// sitting first and steal the Enter.
return [
Expand All @@ -232,22 +237,33 @@ function buildItems(
...focusArcs.map((a) => ({
id: a.id,
label: a.name,
hint: a.id === action.arcId ? "current" : undefined,
hint: a.id === action.arcId ? "current" : parentFocus?.name,
run: () => void updateAction(action.id, { arcId: a.id }),
})),
].filter((item) => matches(item.label));
}
case "move": {
// A move now only changes the focus (PROG-102). Archived focuses aren't
// valid destinations (D26); the action's current focus is excluded.
// Alphabetical (PROG-83).
return sortByName(ws.focuses.filter((p) => !p.archivedAt))
.filter((focus) => focus.id !== action.focusId)
.filter((focus) => matches(focus.name))
// Structure order, not alphabetical (PROG-123 supersedes PROG-83 here):
// focuses group under their workspace in the outline's manual rank, and
// the hint names the parent workspace. A typed query also matches the
// workspace name, since it's on screen.
const workspaceOrder = new Map(sortContainers(ws.workspaces).map((w, i) => [w.id, i]));
const workspaceName = (focus: { workspaceId: string }) =>
ws.workspaces.find((w) => w.id === focus.workspaceId)?.name;
return ws.focuses
.filter((focus) => !focus.archivedAt && focus.id !== action.focusId)
.sort(
(a, b) =>
(workspaceOrder.get(a.workspaceId) ?? 0) - (workspaceOrder.get(b.workspaceId) ?? 0) ||
byRankThenName(a, b),
)
.filter((focus) => matches(focus.name) || matches(workspaceName(focus) ?? ""))
.map((focus) => ({
id: focus.id,
label: focus.name,
hint: "Focus",
hint: workspaceName(focus) ?? "Focus",
run: () => moveAction(action.id, { focusId: focus.id }),
}));
}
Expand Down