Skip to content

fix(viewer): promote action palette into main column + render chronicle paragraphs - #411

Merged
100yenadmin merged 1 commit into
mainfrom
fix/gui-palette-layout-chronicle
May 30, 2026
Merged

fix(viewer): promote action palette into main column + render chronicle paragraphs#411
100yenadmin merged 1 commit into
mainfrom
fix/gui-palette-layout-chronicle

Conversation

@100yenadmin

@100yenadmin 100yenadmin commented May 30, 2026

Copy link
Copy Markdown
Member

Fixes two VERIFIED render bugs on the live play screen (viewer/openworlds/screen-table.jsx). Render/layout only — does not touch server.py / the read model (a sibling PR owns the server-side action gate), the skill, or wire contracts.

G3 — action tools missing / hard to find

The action palette was rendered only in the narrow 320px right-rail "Encounter" panel, and that map did actions.slice(0, 6), silently dropping the last two of the 8 verbs (bonus-action + reaction). There was no palette in the main/center column, so a first-time viewer (or a blind AI playtester) could easily see no way to act besides the free-text box.

Change (layout):

  • The action palette is now promoted into the main/center column — it lives in the Chronicle panel footer, directly above the free-text Declare box, so it is the obvious, primary way to act in the main play flow.
  • Exploration verbs (Say / Do / Check / Continue / Cast / Use) always render; combat verbs (Attack / Bonus / Reaction) render in an "In Combat" group, shown only when in combat (actionsInCombat).
  • The slice(0, 6) truncation is removed — every verb the read model emits renders.
  • actionsInCombat keys off the engine-mutated combat gauge (surface?.encounter?.active or a combat verb being available) — never off fiction (honors the gates/triggers invariant). Uses surface?.encounter?.active rather than the encounter const, which is declared later in the function (avoids a TDZ ReferenceError).
  • The right rail keeps the encounter summary + Round Order and now points to the main-column palette (no divergent second button list).
  • Reuses the existing EncounterButton component + invokeAction/postMove wiring — the click path is unchanged.

G4 — Chronicle is one big text block

The narration branch rendered {text} in a <div className="body"> with default white-space, so a multi-paragraph DM beat (with embedded \n\n) collapsed into one run-on block.

Change: added whiteSpace: "pre-line" to the narration <div> so the DM's blank-line paragraph breaks render as visually separated paragraphs. sanitizeNarration(text) (the GM-advisory strip) is left intact.

How I verified visually

Rendered the real /openworlds/ UI against a seeded campaign (player + companion + active combat + a 3-paragraph narration beat) and looked:

  • Desktop (1280): the palette renders in the center column (verb-button x ≈ 278–471, well left of the ~948px right rail); all 9 verbs present including Attack/Bonus/Reaction (the two the old cap dropped); the "In Combat" subgroup appears; the chronicle beat renders as 3 separated paragraphs (computed white-space: pre-line). 0 console errors.
  • No-combat campaign: the "In Combat" group + Attack/Bonus/Reaction are hidden; the 6 exploration verbs remain in the main column.
  • Tablet: the palette grid (auto-fill minmax(150px)) wraps cleanly.
  • Vendored-babel transpile of the edited JSX is clean.

Tests

Added 3 regression tests to OpenWorldsStaticRouteTests in viewer/tests/test_openworlds_static.py (string-assertion harness mirroring the existing screen-table tests):

  • palette renders in the main column (an EncounterButton appears between the CENTER and RIGHT layout markers),
  • the action list is not truncated (no actions.slice(0, [0-7])) and is split into exploration/combat groups gated on actionsInCombat,
  • the narration render preserves paragraph breaks (whiteSpace: "pre-line") with sanitizeNarration still applied.

Full viewer suite: 92/92 passing locally.


Do NOT close on merge — verify on the next 8799 + .app playtest.

Summary by CodeRabbit

  • New Features

    • Action palette moved to the main column, showing exploration actions and a distinct "In Combat" grid when combat is active
    • Right rail now shows a muted note pointing users to the main Actions area
  • Bug Fixes

    • All actions displayed without truncation
    • Multi-paragraph DM narration preserves paragraph breaks
    • Combat action availability correctly gated by encounter status
  • Tests

    • Added tests verifying action palette placement, full action rendering, and narration paragraph preservation

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 30, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d5ab4792-e1c1-47c1-8039-6b4b68e5a38f

📥 Commits

Reviewing files that changed from the base of the PR and between d4975ba and 7c4226d.

📒 Files selected for processing (2)
  • viewer/openworlds/screen-table.jsx
  • viewer/tests/test_openworlds_static.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • viewer/tests/test_openworlds_static.py

📝 Walkthrough

Walkthrough

The PR groups actions into exploration vs combat, computes an actionsInCombat gate, moves the Actions palette into the main CENTER column above the Declare input (removing per-action buttons from the right Encounter rail), updates the Declare helper text, and preserves DM narration paragraph breaks with whiteSpace: "pre-line". Three tests validate layout, truncation removal, and paragraph preservation.

Changes

Openworlds screen-table action and narration changes

Layer / File(s) Summary
Action grouping and combat gating model
viewer/openworlds/screen-table.jsx
Derives explorationActions and combatActions lists and an actionsInCombat boolean based on encounter active status.
Action palette relocation and main-column Actions UI
viewer/openworlds/screen-table.jsx
Adds a main-column "Actions" section rendering exploration verbs and, when actionsInCombat, an "In Combat" grid for combat verbs wired to invokeAction/EncounterButton.
Declare helper text update
viewer/openworlds/screen-table.jsx
Updates the helper sentence under Declare to reference the new Actions palette above.
Right-rail encounter panel update
viewer/openworlds/screen-table.jsx
Removes per-action buttons from the right-side Encounter panel and replaces them with a muted message pointing to the main-column Actions palette.
Narration paragraph break preservation
viewer/openworlds/screen-table.jsx
Sets narration container style to whiteSpace: "pre-line" so sanitized narration preserves blank-line paragraph breaks.
Comprehensive validation tests
viewer/tests/test_openworlds_static.py
Adds three tests: verifying action palette in CENTER co-located with Declare, verifying all actions render without truncation and with grouping/gating, and verifying narration pre-line whitespace with sanitizeNarration(entry.text) kept in the rendering path.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 Actions hop to center ground,

Combat shows when foes abound,
Declare sits with palette near,
Paragraphs stay, now loud and clear,
Tests nibble, green checks appear.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: promoting the action palette into the main column and rendering chronicle paragraphs with proper formatting.
Description check ✅ Passed The description covers the required summary section thoroughly, detailing what changed and why, with comprehensive visual verification and test documentation. However, the Licensing/CLA checklist is not completed.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@viewer/openworlds/screen-table.jsx`:
- Around line 284-292: The computed arrays explorationActions, combatActions and
the flag actionsInCombat are never rendered, so restore the quick-action palette
into the center/main column: add a render block in the ScreenTable component
that maps explorationActions to the MAIN palette and, when actionsInCombat is
true, also maps combatActions (attack/bonus/reaction) into the same palette;
ensure you use explorationActions.map(...) and combatActions.map(...) (or
equivalent render helpers already in this file) and conditionally render
combatActions only when actionsInCombat is true so the verbs appear in the
center column instead of disappearing from the UI.

In `@viewer/tests/test_openworlds_static.py`:
- Around line 214-239: The test asserts for JSX tokens that no longer exist;
update the assertions in
test_openworlds_table_promotes_action_palette_into_main_column to check for the
actual component/strings in the current screen-table.jsx instead of
"<EncounterButton", ">Actions<", "DECLARE: free-text action box",
"explorationActions.map", "combatActions.map", and "onClick={() =>
invokeAction(a)}"; find and replace those expected markers with the real JSX
identifiers used in this PR (the component/prop names rendered for the action
palette and declare box) and make the same corresponding changes in the
duplicate assertion block referenced around lines 240-261 so both tests match
the current JSX output.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7f030b2c-a43d-4173-bf76-140edbd618fe

📥 Commits

Reviewing files that changed from the base of the PR and between 2f40233 and d4975ba.

📒 Files selected for processing (2)
  • viewer/openworlds/screen-table.jsx
  • viewer/tests/test_openworlds_static.py

Comment thread viewer/openworlds/screen-table.jsx
Comment on lines +214 to +239
def test_openworlds_table_promotes_action_palette_into_main_column(self):
# #G3: the action palette must be PROMINENT in the main play flow, not buried in the
# 320px right rail. It is rendered in the CENTER column (LEFT — Party / CENTER — Scene
# + log / RIGHT — Quests), co-located with the free-text Declare box, so a first-time
# viewer (or a blind AI playtester) sees clickable actions without hunting in a side rail.
status, ctype, body = self._get("/openworlds/screen-table.jsx")

self.assertEqual(status, 200)
self.assertIn("text/babel", ctype)
source = body.decode("utf-8")
# The three layout-region markers are present and ordered LEFT → CENTER → RIGHT.
left = source.index("LEFT — Party")
center = source.index("CENTER — Scene")
right = source.index("RIGHT — Quests")
self.assertLess(left, center)
self.assertLess(center, right)
# An EncounterButton palette renders inside the CENTER column (between the CENTER and
# RIGHT markers) — i.e. the palette is in the main column, not only the right rail.
first_button = source.index("<EncounterButton")
self.assertGreater(first_button, center)
self.assertLess(first_button, right)
# The palette sits with the Declare box (the primary input) in the main action flow.
self.assertIn(">Actions<", source)
self.assertIn("DECLARE: free-text action box", source)
self.assertLess(source.index(">Actions<"), source.index("DECLARE: free-text action box"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Update these assertions to match the actual JSX in this PR.

screen-table.jsx does not currently contain <EncounterButton, >Actions<, DECLARE: free-text action box, explorationActions.map, combatActions.map, or onClick={() => invokeAction(a)}. This block will fail as-is because the checks are asserting against source that is not present here.

Also applies to: 240-261

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@viewer/tests/test_openworlds_static.py` around lines 214 - 239, The test
asserts for JSX tokens that no longer exist; update the assertions in
test_openworlds_table_promotes_action_palette_into_main_column to check for the
actual component/strings in the current screen-table.jsx instead of
"<EncounterButton", ">Actions<", "DECLARE: free-text action box",
"explorationActions.map", "combatActions.map", and "onClick={() =>
invokeAction(a)}"; find and replace those expected markers with the real JSX
identifiers used in this PR (the component/prop names rendered for the action
palette and declare box) and make the same corresponding changes in the
duplicate assertion block referenced around lines 240-261 so both tests match
the current JSX output.

…le paragraphs

G3 (action palette): the action palette rendered ONLY in the 320px right
rail and the right-rail map capped at actions.slice(0, 6), silently
dropping the last two verbs (bonus-action + reaction). A first-time
viewer (or a blind AI playtester) could miss every way to act besides
free-text.
  - Promote the palette into the MAIN/center column, in the Chronicle
    panel footer directly above the Declare box, so it is the obvious
    primary way to act. Exploration verbs (Say/Do/Check/Continue/Cast/
    Use) always show; combat verbs (Attack/Bonus/Reaction) show in an
    'In Combat' group gated on actionsInCombat.
  - Remove the slice(0, 6) cap — every verb the read model emits renders.
  - actionsInCombat keys off the engine-mutated combat gauge
    (encounter.active / a combat verb being available), never fiction.
  - Right rail keeps the encounter summary + Round Order and points to
    the main-column palette (no divergent second button list).
  - Reuses EncounterButton + invokeAction + ACTION_HINTS — click path
    unchanged.

G4 (chronicle paragraphs): the narration branch rendered {text} in a
div.body with default white-space, collapsing a multi-paragraph DM beat
into one run-on block. Add whiteSpace:'pre-line' so the DM's blank-line
paragraph breaks render as separated paragraphs. sanitizeNarration is
untouched.

Tests: 3 regression tests added to OpenWorldsStaticRouteTests (palette in
main column, no truncation + group split, narration preserves paragraph
breaks). Full viewer suite 92/92. Verified live via the seeded /openworlds
table screen (desktop + no-combat + tablet).
@100yenadmin
100yenadmin force-pushed the fix/gui-palette-layout-chronicle branch from d4975ba to 7c4226d Compare May 30, 2026 22:20
@100yenadmin
100yenadmin merged commit 794d50e into main May 30, 2026
9 checks passed
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