Start a character and Level up list only what the character can take; a chosen ancestry feat shows on its step (#1121) #1124

Merged
claude-bot merged 3 commits from fix/1121-relevant-choices into main 2026-10-11 21:53:02 +00:00
Collaborator

Items 2 and 3 of the owner's lane 11 review (#1121), with the owner's Q167 answer (b): "list only what can be taken right now". Decision D291; it supersedes D261 for the listing.

What changes

  • A chosen ancestry feat shows on its step (bug). "Choose 1 ancestry feat" did add the feat, but the step never rendered it and read its count from the definition instead of the live sheet, so it looked as if nothing happened and each retry added a duplicate row with no slot or level. The step now lists what was chosen, with Remove; the count comes from the sheet's creation.steps; Add closes once the choice is met; the server sets slot and level: 1 and refuses a second feat for a met choice.
  • Pickers narrow to the character, in the query. The picker takes category, ancestry_id and class_id; feats are narrowed by category, level and the ancestry or class they are a trait of, heritages by fields.ancestry. A general or skill feat is never hidden by ownership; an ancestry or class typed by hand (D250) narrows nothing. The narrowing is SQL conditions passed into search_service.search (new extra, offset), not a filter over one capped page: an earlier draft filtered the first 100 results and could come back wrongly empty with a large pack. Two tests flood the content with 120 other-ancestry feats that sort first and fail on that draft.
  • Level up lists only what can be taken now. options omits every feat feat_why refuses (another class or ancestry, "From level N", "Needs …", "Already on the sheet.") and pages through the content until it has OPTIONS_LIMIT takeable feats or runs out. The apply-time refusal stays, so a hidden feat still cannot be forced through the API. The disabled-with-reason rendering is gone from the Level up feat picker; an empty list reads "No feats to take at this level in the loaded content."
  • Mini pack: dwarf and elf ancestry feats and an elf heritage, so a dwarf or elf has something to pick on dev and in tests.
  • D291 in sources/intro.md, the IS-437 ledger row, and CATALOGUE.md edited by hand to match (pwsh was not available to run build.ps1).

Checks

  • Backend tests/sheets on the dev server: 756 passed. Ruff 0.4.4 format and check: clean.
  • npx vitest run src/pages/sheet: 232 passed. eslint: 0 errors.
  • scripts/changelog.py check, scripts/check_version_sync.py: OK. No /api/bot/* change.
  • Not run locally: the journeys. e2e/j7-a-character.spec.js now picks a heritage and an ancestry feat at creation and asserts an untakeable feat is not listed at level up; CI is its first run.

Known limits

  • At Start a character the picker narrows by category, owner and level, but does not hide a feat whose prerequisite is unmet (that check needs level-up's context). Level 1 feats rarely have one.
  • 5e is unchanged: its mini pack has no field saying which class owns an entry.

🤖 Generated with Claude Code

Items 2 and 3 of the owner's lane 11 review (#1121), with the owner's Q167 answer (b): "list only what can be taken right now". Decision D291; it supersedes D261 for the listing. ## What changes - **A chosen ancestry feat shows on its step (bug).** "Choose 1 ancestry feat" did add the feat, but the step never rendered it and read its count from the definition instead of the live sheet, so it looked as if nothing happened and each retry added a duplicate row with no slot or level. The step now lists what was chosen, with Remove; the count comes from the sheet's `creation.steps`; Add closes once the choice is met; the server sets `slot` and `level: 1` and refuses a second feat for a met choice. - **Pickers narrow to the character, in the query.** The picker takes `category`, `ancestry_id` and `class_id`; feats are narrowed by category, level and the ancestry or class they are a trait of, heritages by `fields.ancestry`. A general or skill feat is never hidden by ownership; an ancestry or class typed by hand (D250) narrows nothing. The narrowing is SQL conditions passed into `search_service.search` (new `extra`, `offset`), not a filter over one capped page: an earlier draft filtered the first 100 results and could come back wrongly empty with a large pack. Two tests flood the content with 120 other-ancestry feats that sort first and fail on that draft. - **Level up lists only what can be taken now.** `options` omits every feat `feat_why` refuses (another class or ancestry, "From level N", "Needs …", "Already on the sheet.") and pages through the content until it has `OPTIONS_LIMIT` takeable feats or runs out. The apply-time refusal stays, so a hidden feat still cannot be forced through the API. The disabled-with-reason rendering is gone from the Level up feat picker; an empty list reads "No feats to take at this level in the loaded content." - **Mini pack:** dwarf and elf ancestry feats and an elf heritage, so a dwarf or elf has something to pick on dev and in tests. - D291 in `sources/intro.md`, the IS-437 ledger row, and `CATALOGUE.md` edited by hand to match (`pwsh` was not available to run `build.ps1`). ## Checks - Backend `tests/sheets` on the dev server: 756 passed. Ruff 0.4.4 format and check: clean. - `npx vitest run src/pages/sheet`: 232 passed. eslint: 0 errors. - `scripts/changelog.py check`, `scripts/check_version_sync.py`: OK. No `/api/bot/*` change. - **Not run locally:** the journeys. `e2e/j7-a-character.spec.js` now picks a heritage and an ancestry feat at creation and asserts an untakeable feat is not listed at level up; CI is its first run. ## Known limits - At Start a character the picker narrows by category, owner and level, but does not hide a feat whose prerequisite is unmet (that check needs level-up's context). Level 1 feats rarely have one. - 5e is unchanged: its mini pack has no field saying which class owns an entry. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Start a character: a feat added to a choose row is listed under it with
Remove, counted from the live sheet, takes its slot and level, and closes
Add once met. Pickers narrow feats by category, level and owning ancestry
or class, and heritages by ancestry. Level up no longer lists another
class's or ancestry's feat (D291, supersedes D261 in part). Mini pack gains
dwarf and elf entries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix(sheets): narrow pickers in the query; list only what can be taken now (#1121)
Some checks failed
CI / Does this change touch the recording path? (pull_request) Successful in 18s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been skipped
CI / Bot/backend version sync (pull_request) Successful in 28s
CI / Backend lint (ruff) (pull_request) Successful in 52s
CI / Feedback site tests (pull_request) Successful in 59s
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 1m30s
CI / Bot tests and audit (pull_request) Successful in 2m34s
CI / Docker image build (pull_request) Successful in 5m10s
CI / End-to-end journeys (desktop) (pull_request) Has been cancelled
CI / Backend migration, tests, and audit (pull_request) Has been cancelled
CI / End-to-end journeys (keyboard) (pull_request) Has been cancelled
CI / Frontend (shard 1/2) (pull_request) Has been cancelled
CI / End-to-end journeys (phone) (pull_request) Has been cancelled
CI / Frontend (shard 2/2) (pull_request) Has been cancelled
fd69a37e32
The picker's category, ancestry/class and heritage narrowing is part of the
search (SQL) instead of a filter on one capped page, and search pages on
with an offset. Level up options reads pages until it has OPTIONS_LIMIT
takeable feats and omits every feat feat_why refuses (Q167, D291). The
disabled-with-reason feat rendering is gone from the Level up picker;
sources/intro.md, the IS-437 ledger row and CATALOGUE.md follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix(sheets): bound the level-up feat scan; level and already-taken in the query (#1121)
All checks were successful
CI / Does this change touch the recording path? (pull_request) Successful in 19s
CI / Docker image build (pull_request) Successful in 20s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been skipped
CI / Bot/backend version sync (pull_request) Successful in 28s
CI / Backend lint (ruff) (pull_request) Successful in 34s
CI / Feedback site tests (pull_request) Successful in 50s
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 1m3s
CI / Bot tests and audit (pull_request) Successful in 1m56s
CI / Frontend (shard 2/2) (pull_request) Successful in 10m3s
CI / Frontend (shard 1/2) (pull_request) Successful in 12m4s
CI / End-to-end journeys (phone) (pull_request) Successful in 14m0s
CI / End-to-end journeys (keyboard) (pull_request) Successful in 15m20s
CI / End-to-end journeys (desktop) (pull_request) Successful in 15m24s
CI / Backend migration, tests, and audit (pull_request) Successful in 33m28s
82fa9ed3aa
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign in to join this conversation.
No description provided.