Skip to content

Merge pull request #928 from genesis-ai-dev/main#929

Open
Luke-Bilhorn wants to merge 25 commits into
mainfrom
867-make-milestone-subdivisions-for-rendering-editable
Open

Merge pull request #928 from genesis-ai-dev/main#929
Luke-Bilhorn wants to merge 25 commits into
mainfrom
867-make-milestone-subdivisions-for-rendering-editable

Conversation

@Luke-Bilhorn
Copy link
Copy Markdown
Contributor

No description provided.

Luke-Bilhorn and others added 19 commits April 22, 2026 16:39
Introduce a dedicated subdivision model that lets milestones describe
custom rendering breaks, while preserving today's arithmetic 50-cell
chunking as the default fallback. Pagination, subsection lookup, and
subsection progress now all go through a single resolver, so future
writes to `metadata.data.subdivisions` immediately change what the
editor renders without touching any other code path.

- Types: add `MilestoneSubdivisionPlacement`, `SubdivisionInfo`, and
  `metadata.data.subdivisions` / `metadata.data.subdivisionNames` on
  milestone cells; extend `MilestoneInfo` with resolved `subdivisions`
  and add `updateMilestoneSubdivisions` / `updateMilestoneSubdivisionName`
  editor messages for upcoming writers.
- Resolver (`utils/subdivisionUtils.ts`): pure function that returns
  ordered, non-overlapping subdivisions for a milestone. Handles
  arithmetic fallback, stable ordering, anchor pruning on deleted cells,
  duplicate collapse, and target-side name overrides via
  `FIRST_SUBDIVISION_KEY`.
- CodexCellDocument: `buildMilestoneIndex`, `getCellsForMilestone`,
  `getSubsectionCountForMilestone`, `findMilestoneAndSubsectionForCell`,
  and `calculateSubsectionProgress` now share the resolver. Behavior is
  byte-identical for documents without custom subdivisions.
- Tests: add unit + integration coverage for the resolver and the
  document APIs (custom breaks, slicing, stale anchor pruning, legacy
  50-cell behavior).

Made-with: Cursor
Wire up `updateMilestoneSubdivisions` and `updateMilestoneSubdivisionName`
editor messages so the UI can now persist custom subdivision breaks and
names. Placements are source-authoritative — edits from a `.codex`
document are rejected and mirrored onto the paired source's target only
after a root-cell-ID sanity check confirms the two milestones line up.
Names live on a separate `subdivisionNames` map per document so source
and target can be renamed independently.

- Message handlers: validate anchors against current root content cells,
  strip names on mirror (target uses its own override map), recover
  gracefully when the paired document diverges, and refresh the webview
  via the existing milestone-refresh path.
- Provider helpers: `getPairedNotebookUri` and
  `getOrOpenDocumentForUri` keep the paired-document plumbing localized
  to the provider and reuse any already-open `CodexCellDocument`.
- Document: expose `getRootContentCellIdsForMilestone` for anchor
  validation / pairing checks, and invalidate the milestone-index cache
  whenever `subdivisions` or `subdivisionNames` change so renames and
  break edits appear on the next render.
- Tests: cover anchor-set extraction (including paratext / child /
  deleted exclusions), cache invalidation on both subdivisions and name
  overrides, and the full round-trip from placements → arithmetic
  fallback when placements are cleared.

Made-with: Cursor
The editor now prefers provider-computed subdivisions when building the
UI's `Subsection` list, so custom milestone breaks and their names flow
through to navigation the moment the resolver emits them. The prior
arithmetic calculation remains as a pure fallback for the brief window
before the first `milestoneIndex` lands (or for any stale payloads
missing `subdivisions`), which preserves today's behaviour exactly for
notebooks without custom breaks.

- Extract the subsection-building logic into
  `CodexCellEditor/utils/subdivisionUtils.ts` so it can be unit-tested
  independently and reused by future tail-append / renaming paths.
- Extend the `Subsection` type with `name`, `key`, `startCellId`, and
  `source` so downstream UI (accordion, header) can display names, tell
  custom breaks from auto ones, and echo the stable key back to the
  provider on renames.
- Add unit tests covering empty milestones, subdivision preference,
  arithmetic fallback parity, ID stability, and edge-case page sizes.

Made-with: Cursor
Each keyed subsection in the milestone accordion now carries a hover
"rename" affordance. Editing is inline, works on both source and target
documents (names are stored per-document in `subdivisionNames`), and an
optimistic local cache keeps the new name on screen while the provider
refreshes. Numeric ranges remain visible alongside the name so users
never lose track of where a subdivision starts and ends.

- Skip the affordance entirely for legacy subsections without a `key`
  (no resolver payload, nothing to persist) so the UI degrades cleanly.
- Clearing the input posts an empty `newName`, which the provider
  handler interprets as "remove override" and falls back to the numeric
  range.
- Pressing Enter saves, Escape cancels; cancel never posts a message.
- Add focused tests covering affordance gating, display behaviour, the
  save/clear/no-op paths, and cancel.

Made-with: Cursor
Source-side authors can now remove an individual custom subdivision
break and, if they want to start over, reset all custom breaks in a
milestone back to the default arithmetic chunking.

Both actions post `updateMilestoneSubdivisions` with a recomputed
placement list derived from the resolver's subdivisions (custom-index-
>0 with a stable startCellId). The provider already mirrors the new
list to the paired target document, so source and target stay aligned.

- The × button only appears for subdivisions whose `source === "custom"`
  and that carry a `startCellId`, so legacy payloads and the implicit
  first subdivision are untouched.
- "Reset to default breaks" uses an in-place two-click confirmation
  (arm-then-commit, auto-disarming after 3s) to avoid accidental loss
  without pulling in a modal. The accessible label flips between
  "Reset Subdivisions" and "Confirm Reset Subdivisions" so assistive
  tech announces the armed state.
- Neither control is rendered on target documents; placements are
  source-authoritative and the provider rejects writes from target
  URIs anyway — the UI just omits the affordance up front.
- Extend the lucide-react test mock with the new X/Undo2 icons and add
  focused coverage for the control visibility and both post shapes.

Made-with: Cursor
Extract the save/mirror/refresh pipeline from updateMilestoneSubdivisions
into commitMilestoneSubdivisionPlacements so a second entry point — a
source-only add-break command that takes a 1-based cellNumber — can reuse
the same source-authoritative guarantees (root-cell validation, target
mirroring with names stripped, divergence-skip on root mismatch).

Tests exercise the new handler via a test-only export of the handler map,
covering the happy path, the preserve-names-on-append path, out-of-range
rejection, idempotence on re-add, and the non-source rejection guard.

Made-with: Cursor
…nly)

Source-side users can now split a milestone by typing the target cell
number directly into the accordion footer instead of having to touch the
underlying data. The form validates the number against the milestone's
root-cell range before posting addMilestoneSubdivisionAnchor, surfaces
an inline error on out-of-range input, and respects Escape/Cancel to
close without committing. Hidden entirely on target documents and on
milestones with fewer than 2 cells.

Made-with: Cursor
…bels

Introduces codex-editor-extension.useSubdivisionNumberLabels (default false).
When enabled, the milestone accordion always shows the numeric cell range
instead of a user-assigned subdivision name. The provider broadcasts the
current value on initial pagination load and whenever the setting changes.

Made-with: Cursor
…ings

Adds a Pagination & Subdivisions section to the Interface Settings webview
so users can adjust the default milestone page size and toggle whether
subdivisions render as numeric ranges instead of their names. The panel
listens for configuration changes and rebroadcasts initial data when either
setting is updated from elsewhere.

Made-with: Cursor
… edits

When a user adds, removes, or resets subdivision breaks on a source
document, the placement mirror was already being persisted to the paired
target file but the open target webview was never told to re-render —
users had to close and reopen the target tab to see the change. This
threads the existing milestone-refresh path through to the target panel
so the change appears immediately.

The cost per source-side break edit is one extra postMessage plus a
milestone-index rebuild on the target (cached and invalidated by
updateCellData), which is negligible even on older hardware. When no
target webview is open the refresh is skipped silently and the target
picks up the change on next load via the persisted file.

Subdivision name edits remain per-side by design and don't trigger a
target refresh.

Made-with: Cursor
The milestone accordion now opens in a read-only baseline: the only
edit-related control in the header is a gear icon. Clicking the gear
reveals the title pencil, surfaces the per-subsection rename pencils
(always visible — no more hover-only reveal), and unhides the source-
only "Add break…" / "Reset" footers. Re-clicking the gear collapses the
affordances back, and reopening the dropdown always starts from the
read-only baseline so the gear-on state is never sticky across sessions.

The "Add break at cell" placeholder also drops its compact "2–N" range
hint in favor of a single illustrative number, matching the underlying
input semantics (one cell number, not a range).

Tests opt back into settings mode by default via a new `initialSettingsMode`
prop so the existing edit/rename/break test suites keep exercising those
flows without first having to click the gear; three new tests cover the
gear toggle itself.

Made-with: Cursor
…istence

- resolveSubdivisions now sub-chunks long stretches between user-defined
  breaks by cellsPerPage, so adding a break at cell 433 in a 700-cell
  milestone still produces [1-50]…[401-432],[433-482]…[683-700] rather
  than two huge pages.
- New workspace setting codex-editor-extension.maxSubdivisionLength (0 =
  off): when positive, stretches between user breaks shorter than this
  value are left intact (e.g. a 103-cell logical page survives with
  threshold=120), while longer stretches continue to be sub-chunked.
  maxSubdivisionLength is threaded through buildMilestoneIndex, getCells-
  ForMilestone, findMilestoneAndSubsectionForCell, and the config-change
  listener so all callers stay in sync.
- Source subdivision names are now the default for the paired target
  document. Names ride on the placement object and are mirrored into
  subdivisionNamesFromSource; resolveSubdivisions picks them up as a
  fallback when the target has no local override. A target translator
  can still set their own independent name.
- Naming an auto-generated chunk (on the source side) promotes it to a
  persistent placement so that changes to cellsPerPage or maxSubdivision-
  Length cannot displace or orphan the name. Clearing a name leaves the
  placement intact; the implicit first subdivision is never promoted.
- Cache-invalidation fix: updateCellData now also busts the milestone
  index when subdivisionNamesFromSource changes.
- 24 new unit / integration tests covering all of the above.

Made-with: Cursor
- New "Maximum subdivision length" toggle + numeric input in the
  Pagination & Subdivisions section. When enabled, defaults to
  cellsPerPage × 2. Wired to the codex-editor-extension.maxSubdivision-
  Length workspace setting with validation (clamped 1-5000).
- Temporarily hides the "Always show subdivision number ranges" toggle
  (the underlying state and message wiring are preserved); names are
  always shown since numeric ranges are already visible in lighter text.
- Removes the "(between 5 and 200)" parenthetical from the Cells per
  page description.
- Updates the Maximum subdivision length description to: "Pagination
  allows ranges between user added subdivisions up to this length".

Made-with: Cursor
- Replace the per-subdivision X remove button and the milestone-level
  reset button icons with Trash2 (lucide), tinted vscode-errorForeground.
- Rows that cannot be deleted (auto-chunks, target side, first
  subdivision) now render an invisible aria-hidden phantom span the same
  size as the trash-can button (28×28 px, visibility:hidden) so the
  rename pencil icons stay vertically aligned across all rows.

Made-with: Cursor
This keeps row spacing consistent with the active delete button in milestone settings mode.
Adds a touch more separation between row controls and progress dots.
Ensure the clicked milestone expands before entering edit mode, and move the rename affordance into the milestone row.
Test 2, hitting big green merge button
@Luke-Bilhorn Luke-Bilhorn linked an issue Apr 28, 2026 that may be closed by this pull request
@Luke-Bilhorn
Copy link
Copy Markdown
Contributor Author

/build

@github-actions
Copy link
Copy Markdown

Build failed: https://github.com/genesis-ai-dev/codex-editor/actions/runs/25146965413

@Luke-Bilhorn
Copy link
Copy Markdown
Contributor Author

/build

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 1, 2026

Build failed: https://github.com/genesis-ai-dev/codex-editor/actions/runs/25229400367

The /build CI was failing because the webview test suite couldn't load
MilestoneAccordion: the lucide-react mock explicitly listed icons and
silently broke the moment the component imported a new one (Trash2).
Even after fixing the mock, ~30 assertions still pointed at the old
single-pencil "Edit Milestone" label that no longer exists, and the new
per-row "Rename Milestone" pencils caused getByLabelText to throw on
multi-element matches.

Two coupled fixes, one outcome:

1. Standardize rename-flow aria-labels around the user-facing nouns:
   - Milestone rename: "Rename Milestone" / "Save Milestone Rename" /
     "Cancel Milestone Rename" (was "Edit/Save Milestone" + "Revert
     Changes").
   - Milestone subdivision rename: "Rename/Save/Cancel Milestone
     Subdivision Rename" (was "Rename/Save/Cancel Subsection [Name]").
   Non-rename actions (gear, add/remove break, reset) keep their
   existing names.

2. Make the test queries match the new UI:
   - vi.mock("lucide-react") now uses importOriginal so newly imported
     icons don't silently break the suite again.
   - Per-milestone rename pencils are looked up via a small helper that
     scopes within the row's data-testid="accordion-item-{idx}", so
     tests read as "Chapter 1's pencil" rather than "the first
     getByLabelText match".
   - Describe blocks renamed Edit Mode -> Milestone Rename and
     Subsection Rename -> Milestone Subdivision Rename to match the
     user-facing language.
@Luke-Bilhorn
Copy link
Copy Markdown
Contributor Author

/build

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 7, 2026

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.

Make milestone subdivisions for rendering editable

1 participant