Level up shows another class's feat disabled, with why (#1008) #1015

Closed
claude-bot wants to merge 0 commits from fix/l19-other-class-feats into main
Collaborator

Closes #1008. The owner's answer to Q133 (L19), recorded as D261.

At a class feat step every class feat in the loaded content was offered as if the character could take it: a feat carries its class as a trait, not a prerequisite, so a Fighter was offered a rogue's feat.

What changes

  • The rule is the server's, in the place prerequisites already live (sheet_levelup.feat_why). At a class feat step, a content feat whose traits name classes but not the character's is unavailable, with the reason "A rogue feat." ("An alchemist feat.", "A rogue or ranger feat."). A feat that names the character's class among several is available; a feat with no class trait is untouched.
  • It is worked out from the loaded content (class_rule: the system's class entries), with no list of class names in the code. A character whose class is typed in rather than chosen (D250) has nothing disabled on class grounds.
  • Because it goes through feat_why, the options list, the preview and Reach level n all agree: the page shows the feat disabled with its reason, exactly as it shows an unmet prerequisite, and applying a level with it is refused. A feat typed by name still passes (D250).
  • The list's short note beside such a feat reads "another class", not "not yet": it never becomes takeable.
  • IS-437's states column now names this state. No new UI pattern.

Start a character

The question said creation behaves the same way. It doesn't have the step: PF2e's creation offers only the ancestry feat, through the plain content picker, which has no disabled-with-why state at all. So nothing changes there, and giving that picker such a state would be new design. 5e has no class feat step either.

Checked

  • Full backend suite: 6698 passed, 14 skipped; ruff 0.4.4 clean. Full vitest: 3580 passed, 1 skipped (one run, then the sheet tests again after a test-only fix: 218 passed); eslint 0 errors.
  • Rendered in an e2e stack as the character's player (J7's Fighter, level 1 to 2) at 390×844 (touch) and 1440×900, light and dark, and looked at: Reactive Shield and Sudden Charge first and takeable; Gang Up, Nimble Dodge, Quick Draw and Skirmish Strike after them, each "Class · another class"; choosing Gang Up shows "A rogue feat." and a disabled "Choose Gang Up" (aria-disabled, described by the reason). "not yet" appears nowhere on the page. No sideways scroll, console errors or failed calls.
  • J7 needed no change (its Fighter picks Sudden Charge). No migration, no bot API change. Changelog fragment; every coverage/lane*.py passes.

🤖 Generated with Claude Code

Closes #1008. The owner's answer to Q133 (L19), recorded as D261. At a class feat step every class feat in the loaded content was offered as if the character could take it: a feat carries its class as a trait, not a prerequisite, so a Fighter was offered a rogue's feat. ## What changes - **The rule is the server's, in the place prerequisites already live** (`sheet_levelup.feat_why`). At a class feat step, a content feat whose traits name classes but not the character's is unavailable, with the reason "A rogue feat." ("An alchemist feat.", "A rogue or ranger feat."). A feat that names the character's class among several is available; a feat with no class trait is untouched. - It is worked out from the loaded content (`class_rule`: the system's class entries), with no list of class names in the code. A character whose class is typed in rather than chosen (D250) has nothing disabled on class grounds. - Because it goes through `feat_why`, the options list, the preview and Reach level n all agree: the page shows the feat disabled with its reason, exactly as it shows an unmet prerequisite, and applying a level with it is refused. A feat typed by name still passes (D250). - The list's short note beside such a feat reads "another class", not "not yet": it never becomes takeable. - IS-437's states column now names this state. No new UI pattern. ## Start a character The question said creation behaves the same way. It doesn't have the step: PF2e's creation offers only the ancestry feat, through the plain content picker, which has no disabled-with-why state at all. So nothing changes there, and giving that picker such a state would be new design. 5e has no class feat step either. ## Checked - Full backend suite: **6698 passed, 14 skipped**; ruff 0.4.4 clean. Full vitest: **3580 passed, 1 skipped** (one run, then the sheet tests again after a test-only fix: 218 passed); eslint 0 errors. - Rendered in an e2e stack as the character's player (J7's Fighter, level 1 to 2) at 390×844 (touch) and 1440×900, light and dark, and looked at: Reactive Shield and Sudden Charge first and takeable; Gang Up, Nimble Dodge, Quick Draw and Skirmish Strike after them, each "Class · another class"; choosing Gang Up shows "A rogue feat." and a disabled "Choose Gang Up" (`aria-disabled`, described by the reason). "not yet" appears nowhere on the page. No sideways scroll, console errors or failed calls. - J7 needed no change (its Fighter picks Sudden Charge). No migration, no bot API change. Changelog fragment; every `coverage/lane*.py` passes. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The list's note for another class's feat is tested as it renders (#1008)
Some checks failed
CI / Backend lint (ruff) (pull_request) Successful in 38s
CI / Feedback site tests (pull_request) Successful in 51s
CI / Does this change touch the recording path? (pull_request) Successful in 24s
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 1m6s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been skipped
CI / Bot/backend version sync (pull_request) Successful in 28s
CI / Bot tests and audit (pull_request) Successful in 2m1s
CI / Docker image build (pull_request) Successful in 4m15s
CI / End-to-end journeys (phone) (pull_request) Successful in 8m15s
CI / End-to-end journeys (keyboard) (pull_request) Successful in 9m2s
CI / End-to-end journeys (desktop) (pull_request) Successful in 9m8s
CI / Frontend (shard 2/2) (pull_request) Successful in 14m48s
CI / Frontend (shard 1/2) (pull_request) Successful in 20m4s
CI / Backend migration, tests, and audit (pull_request) Failing after 42m56s
ac5e4ea8b0
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Author
Collaborator

Landed through #1019. This PR's head commit ac5e4ea8 is on main (13f16533): #1019 was built on top of this branch, because both change Level up, and its CI ran the same code in full and went green. This PR's own run lost its backend job when the runner dropped it mid-run at 23:08 UTC on 1 October (the log stops at 50% with nothing failing), so it was never re-run. Closing, since there is nothing left to merge.

**Landed through #1019.** This PR's head commit `ac5e4ea8` is on main (`13f16533`): #1019 was built on top of this branch, because both change Level up, and its CI ran the same code in full and went green. This PR's own run lost its backend job when the runner dropped it mid-run at 23:08 UTC on 1 October (the log stops at 50% with nothing failing), so it was never re-run. Closing, since there is nothing left to merge.
claude-bot closed this pull request 2026-10-02 00:40:42 +00:00
Some checks failed
CI / Backend lint (ruff) (pull_request) Successful in 38s
Required
Details
CI / Feedback site tests (pull_request) Successful in 51s
Required
Details
CI / Does this change touch the recording path? (pull_request) Successful in 24s
Required
Details
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 1m6s
Required
Details
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been skipped
Required
Details
CI / Bot/backend version sync (pull_request) Successful in 28s
Required
Details
CI / Bot tests and audit (pull_request) Successful in 2m1s
Required
Details
CI / Docker image build (pull_request) Successful in 4m15s
Required
Details
CI / End-to-end journeys (phone) (pull_request) Successful in 8m15s
Required
Details
CI / End-to-end journeys (keyboard) (pull_request) Successful in 9m2s
Required
Details
CI / End-to-end journeys (desktop) (pull_request) Successful in 9m8s
Required
Details
CI / Frontend (shard 2/2) (pull_request) Successful in 14m48s
Required
Details
CI / Frontend (shard 1/2) (pull_request) Successful in 20m4s
Required
Details
CI / Backend migration, tests, and audit (pull_request) Failing after 42m56s
Required
Details

Pull request closed

Sign in to join this conversation.
No description provided.