Skip to content

Add Sticker Studio first playable and deploy workflow#2

Merged
wuhuizuo merged 3 commits into
mainfrom
agent/gameplay-engineer/705b616c
May 6, 2026
Merged

Add Sticker Studio first playable and deploy workflow#2
wuhuizuo merged 3 commits into
mainfrom
agent/gameplay-engineer/705b616c

Conversation

@wuhuizuo
Copy link
Copy Markdown
Member

@wuhuizuo wuhuizuo commented May 5, 2026

Summary

  • add the Sticker Studio first playable slice
  • add GitHub Pages preview/stable deploy automation for sticker-studio
  • document preview URL, stable URL, rollback, and first post-deploy checks

Validation

  • npm run verify (sticker-studio)

Summary by CodeRabbit

Release Notes

  • New Features

    • Launched Sticker Studio: a new puzzle game featuring campaign levels, daily challenges, progress persistence, and hint system.
  • Documentation

    • Added comprehensive documentation for setup, testing, and deployment.
  • Tests

    • Added test suite covering game mechanics, level validation, and daily generation.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

Warning

Rate limit exceeded

@wuhuizuo has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 50 minutes and 33 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5776620b-0b4f-4ed0-b3cc-575d7235d588

📥 Commits

Reviewing files that changed from the base of the PR and between a8acf4e and 03b7e6d.

📒 Files selected for processing (1)
  • .github/workflows/sticker-studio-preview.yml
📝 Walkthrough

Walkthrough

Introduces Sticker Studio, a complete browser-based puzzle game vertical slice where players place stickers on pages. Includes game engine with solver validation, campaign and daily level data, application UI with state management, comprehensive styling, GitHub Pages deployment automation, and test coverage.

Changes

Sticker Studio Full Implementation

Layer / File(s) Summary
Data Shape & Constants
sticker-studio/src/data.js, sticker-studio/src/daily.js
Core game data structures: packs, levels, targets, sheets, visuals, storage keys, and deterministic daily level generation with seeded pseudorandom layout/motif selection.
Game Engine & Validation
sticker-studio/src/engine.js
Complete puzzle game logic: state initialization/transitions, legal action collection, target dependency evaluation, clip/sheet management, hint generation, undo support, BFS solver, and comprehensive level validation including overlap/prereq/count constraints.
Application UI & State Wiring
sticker-studio/src/app.js, sticker-studio/index.html, sticker-studio/package.json
Full browser application: global app state with campaign/daily modes, progress persistence via localStorage, event routing, level lifecycle (loadLevel), win/fail transitions, and extensive DOM rendering for hero/rail/board/modal/toolbar UI elements.
Styling & Design System
sticker-studio/styles.css
Complete visual design: CSS variables for color/shadow tokens, responsive grid layouts, card/button/target/sheet-card component styling, modal and tray UI, animations (lift-in, target-pulse), and breakpoint-based responsive adjustments at 920px and 560px.
Deployment & Verification
.github/workflows/sticker-studio-preview.yml, sticker-studio/README.md, sticker-studio/tests/engine.test.js
GitHub Actions workflow with preview (per-PR to gh-pages/previews/pr-*), cleanup on PR close, and stable deployment to gh-pages/sticker-studio/; documentation on run/test/verify/deploy; engine tests covering metadata, validation, action sequences, undo, overflow, hints, daily generation, and park eligibility.

Sequence Diagram

sequenceDiagram
    actor User
    participant App as App (app.js)
    participant Engine as Engine (engine.js)
    participant Data as Data (data.js)
    participant DOM as DOM & localStorage
    
    User->>App: Click target or sheet
    activate App
    App->>Engine: evaluateSheet() or evaluatePlaceAction()
    activate Engine
    Engine->>Data: Retrieve level/target/sheet metadata
    Data-->>Engine: Target/sheet details, prereqs
    Engine->>Engine: Check constraints (clip, prereqs, state)
    Engine-->>App: {ok, message} or error
    deactivate Engine
    
    alt Action valid
        App->>Engine: applyAction() to update state
        activate Engine
        Engine->>Engine: Advance sheet index, update placed targets, clips
        Engine->>Engine: Check for completion or failure
        Engine-->>App: Updated state + outcome (pending/jam/cleared)
        deactivate Engine
        
        App->>App: syncSelectionAfterState()
        App->>DOM: Update DOM, set document.title
        App->>DOM: Save progress to localStorage
        
        alt Level cleared
            App->>Engine: solveLevel() validates solvability
            activate Engine
            Engine->>Engine: BFS solver traces final path
            Engine-->>App: Solution sequence for future reference
            deactivate Engine
            App->>App: Show modal, transition to next/replay
        end
    else Action invalid
        App->>App: Show error message
    end
    
    deactivate App
    User->>DOM: Sees updated board and modal
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

🐰 A studio born of stickers bright,
Where puzzles dance in morning light,
The engine hums, the solver knows,
Each clip and sheet and target shows,
Daily seeds and campaigns deep—
This vertical slice, we keep!

🚥 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly and accurately summarizes the primary changes: adding Sticker Studio gameplay and the deployment automation workflow.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent/gameplay-engineer/705b616c

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

Sticker Studio preview deployed.

URL: https://NightProgrammers.github.io/games/previews/sticker-studio/pr-2/
Commit: 03b7e6d
Path: previews/sticker-studio/pr-2/

If this is the first deployment, enable GitHub Pages once in repository settings and point it at the gh-pages branch.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
sticker-studio/src/app.js (1)

109-134: 💤 Low value

Minor: clamp levelIndex to a valid range, not just an upper bound.

Math.min(levelIndex, HANDCRAFTED_LEVELS.length - 1) only protects against overshoot; a negative levelIndex (e.g. from a future caller, or from Number(target.dataset.index) on malformed markup at Line 827) would index HANDCRAFTED_LEVELS[-1]undefined → crash in createRuntime. A symmetric clamp keeps loadLevel defensible against any future caller.

♻️ Suggested clamp
-  appState.levelIndex = Math.min(levelIndex, HANDCRAFTED_LEVELS.length - 1);
+  appState.levelIndex = Math.max(
+    0,
+    Math.min(levelIndex, HANDCRAFTED_LEVELS.length - 1)
+  );
🤖 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 `@sticker-studio/src/app.js` around lines 109 - 134, The current clamp for
levelIndex in loadLevel only uses Math.min and allows negative indices, which
can make HANDCRAFTED_LEVELS[appState.levelIndex] undefined and crash later in
createRuntime; change the assignment that sets appState.levelIndex (currently
using Math.min(levelIndex, HANDCRAFTED_LEVELS.length - 1)) to a symmetric clamp
that also enforces a minimum of 0 (e.g. compute a clampedIndex = Math.max(0,
Math.min(levelIndex, HANDCRAFTED_LEVELS.length - 1)) and assign it to
appState.levelIndex) so all callers of loadLevel cannot produce out-of-range
indexes when HANDCRAFTED_LEVELS is accessed.
🤖 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 @.github/workflows/sticker-studio-preview.yml:
- Around line 39-47: The workflow runs "Verify Sticker Studio" steps (step name
"Verify Sticker Studio") without installing dependencies first, causing failures
on clean runners; add an install step (e.g., run: npm ci with working-directory:
sticker-studio) immediately before each "run: npm run verify" invocation
(including the other occurrence around lines 150-157) so node_modules are
present when the verify script runs.

In `@sticker-studio/src/app.js`:
- Around line 55-57: Wrap the saveProgress function body in a try/catch so any
exceptions from localStorage.setItem (e.g., QuotaExceededError or platform write
failures) are swallowed or logged without throwing; inside saveProgress (which
currently calls localStorage.setItem(STORAGE_KEY,
JSON.stringify(appState.progress))) try JSON.stringify and setItem, and in catch
handle the error (e.g., console.warn or a no-op) so calls from recordWin won’t
propagate exceptions and abort the UI.
- Around line 840-846: The change handler bound on root (root.addEventListener
... data-action='daily-seed') updates appState.dailySeed but doesn't refresh the
UI; modify that handler to, after setting appState.dailySeed (or using
dailySeedFromDate()), call the same refresh logic used when opening daily:
invoke loadLevel(...) with the new seed and then call render() (or the existing
function that re-renders the active page) so the active daily page (title,
validation, art, reward) updates immediately when the date input changes.

In `@sticker-studio/styles.css`:
- Around line 214-263: Add a visible keyboard focus indicator by defining
:focus-visible styles for .rail-chip, .ghost-button, .primary-button,
.target-node, and .sheet-card (use the :focus-visible pseudo-class so mouse
focus is unaffected). Implement a high-contrast, rounded indicator that respects
existing border-radius (e.g., an outline or outer box-shadow with a 2–4px
thickness and accessible color from your palette) and ensure it doesn't change
layout (use outline or box-shadow, not borders). Apply the rule to those
selectors (e.g., ".rail-chip:focus-visible, .ghost-button:focus-visible,
.primary-button:focus-visible, .target-node:focus-visible,
.sheet-card:focus-visible") so keyboard users can clearly see which control has
focus.

---

Nitpick comments:
In `@sticker-studio/src/app.js`:
- Around line 109-134: The current clamp for levelIndex in loadLevel only uses
Math.min and allows negative indices, which can make
HANDCRAFTED_LEVELS[appState.levelIndex] undefined and crash later in
createRuntime; change the assignment that sets appState.levelIndex (currently
using Math.min(levelIndex, HANDCRAFTED_LEVELS.length - 1)) to a symmetric clamp
that also enforces a minimum of 0 (e.g. compute a clampedIndex = Math.max(0,
Math.min(levelIndex, HANDCRAFTED_LEVELS.length - 1)) and assign it to
appState.levelIndex) so all callers of loadLevel cannot produce out-of-range
indexes when HANDCRAFTED_LEVELS is accessed.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: d6057a75-2c2a-46a7-83aa-8cb90eef7322

📥 Commits

Reviewing files that changed from the base of the PR and between 95c18dc and a8acf4e.

📒 Files selected for processing (10)
  • .github/workflows/sticker-studio-preview.yml
  • sticker-studio/README.md
  • sticker-studio/index.html
  • sticker-studio/package.json
  • sticker-studio/src/app.js
  • sticker-studio/src/daily.js
  • sticker-studio/src/data.js
  • sticker-studio/src/engine.js
  • sticker-studio/styles.css
  • sticker-studio/tests/engine.test.js

Comment on lines +39 to +47
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 22

- name: Verify Sticker Studio
working-directory: sticker-studio
run: npm run verify

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 | 🟠 Major | ⚡ Quick win

Install dependencies before running verification.

Line 44 and Line 155 run npm run verify without an install step. On clean runners, this can fail due to missing node_modules, breaking both preview and stable deploys.

Suggested fix
       - name: Set up Node.js
         uses: actions/setup-node@v4
         with:
           node-version: 22
+          cache: npm
+          cache-dependency-path: sticker-studio/package-lock.json
+
+      - name: Install dependencies
+        working-directory: sticker-studio
+        run: npm ci
 
       - name: Verify Sticker Studio
         working-directory: sticker-studio
         run: npm run verify
@@
       - name: Set up Node.js
         uses: actions/setup-node@v4
         with:
           node-version: 22
+          cache: npm
+          cache-dependency-path: sticker-studio/package-lock.json
+
+      - name: Install dependencies
+        working-directory: sticker-studio
+        run: npm ci
 
       - name: Verify Sticker Studio
         working-directory: sticker-studio
         run: npm run verify

Also applies to: 150-157

🤖 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 @.github/workflows/sticker-studio-preview.yml around lines 39 - 47, The
workflow runs "Verify Sticker Studio" steps (step name "Verify Sticker Studio")
without installing dependencies first, causing failures on clean runners; add an
install step (e.g., run: npm ci with working-directory: sticker-studio)
immediately before each "run: npm run verify" invocation (including the other
occurrence around lines 150-157) so node_modules are present when the verify
script runs.

Comment thread sticker-studio/src/app.js
Comment on lines +55 to +57
function saveProgress() {
localStorage.setItem(STORAGE_KEY, JSON.stringify(appState.progress));
}
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 | 🟡 Minor | ⚡ Quick win

Wrap saveProgress in a try/catch.

localStorage.setItem can throw QuotaExceededError, and Safari's "Prevent cross-site tracking"/private mode and some embedded WebViews throw on every write. Since loadProgress already tolerates failures, saveProgress (called from recordWin after every clear) should match — otherwise a single failing write surfaces as an uncaught exception inside the click handler and aborts the win-modal render.

🛡️ Suggested defensive write
 function saveProgress() {
-  localStorage.setItem(STORAGE_KEY, JSON.stringify(appState.progress));
+  try {
+    localStorage.setItem(STORAGE_KEY, JSON.stringify(appState.progress));
+  } catch {
+    // Storage may be unavailable (private mode, quota); progress is still
+    // tracked in-memory for the current session.
+  }
 }
🤖 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 `@sticker-studio/src/app.js` around lines 55 - 57, Wrap the saveProgress
function body in a try/catch so any exceptions from localStorage.setItem (e.g.,
QuotaExceededError or platform write failures) are swallowed or logged without
throwing; inside saveProgress (which currently calls
localStorage.setItem(STORAGE_KEY, JSON.stringify(appState.progress))) try
JSON.stringify and setItem, and in catch handle the error (e.g., console.warn or
a no-op) so calls from recordWin won’t propagate exceptions and abort the UI.

Comment thread sticker-studio/src/app.js
Comment on lines +840 to +846
root.addEventListener("change", (event) => {
const target = event.target.closest("[data-action='daily-seed']");
if (!target) {
return;
}
appState.dailySeed = target.value || dailySeedFromDate();
});
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 | 🟡 Minor | ⚡ Quick win

Daily seed change does not refresh the active daily page.

When the user is already in daily mode and edits the date input, this handler updates appState.dailySeed but neither calls loadLevel(...) nor render(). The foil page on screen continues to show the previous seed (level title, validation, page art, reward detail) until the user explicitly re-opens the daily, which is confusing because the seeded preview UX implies the page tracks the picker.

🪄 Suggested behavior
 root.addEventListener("change", (event) => {
   const target = event.target.closest("[data-action='daily-seed']");
   if (!target) {
     return;
   }
-  appState.dailySeed = target.value || dailySeedFromDate();
+  const nextSeed = target.value || dailySeedFromDate();
+  appState.dailySeed = nextSeed;
+  if (appState.mode === "daily") {
+    loadLevel({ mode: "daily", dailySeed: nextSeed, showStart: false });
+  }
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
root.addEventListener("change", (event) => {
const target = event.target.closest("[data-action='daily-seed']");
if (!target) {
return;
}
appState.dailySeed = target.value || dailySeedFromDate();
});
root.addEventListener("change", (event) => {
const target = event.target.closest("[data-action='daily-seed']");
if (!target) {
return;
}
const nextSeed = target.value || dailySeedFromDate();
appState.dailySeed = nextSeed;
if (appState.mode === "daily") {
loadLevel({ mode: "daily", dailySeed: nextSeed, showStart: false });
}
});
🤖 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 `@sticker-studio/src/app.js` around lines 840 - 846, The change handler bound
on root (root.addEventListener ... data-action='daily-seed') updates
appState.dailySeed but doesn't refresh the UI; modify that handler to, after
setting appState.dailySeed (or using dailySeedFromDate()), call the same refresh
logic used when opening daily: invoke loadLevel(...) with the new seed and then
call render() (or the existing function that re-renders the active page) so the
active daily page (title, validation, art, reward) updates immediately when the
date input changes.

Comment thread sticker-studio/styles.css
Comment on lines +214 to +263
.rail-chip,
.ghost-button,
.primary-button {
border: 0;
border-radius: 999px;
transition: transform 160ms ease, box-shadow 160ms ease, background-color 160ms ease;
}

.rail-chip {
min-height: 44px;
background: rgba(255, 251, 247, 0.84);
border: 1px solid rgba(98, 74, 48, 0.14);
color: var(--ink);
font-weight: 700;
}

.rail-chip.is-active,
.rail-chip:hover,
.ghost-button:hover,
.primary-button:hover,
.sheet-card:hover,
.target-node:hover {
transform: translateY(-1px);
}

.rail-chip.is-cleared {
box-shadow: inset 0 0 0 1px rgba(107, 150, 110, 0.36);
}

.rail-chip.is-locked {
opacity: 0.45;
}

.ghost-button,
.primary-button {
padding: 12px 18px;
font-weight: 700;
}

.ghost-button {
background: rgba(255, 251, 247, 0.86);
border: 1px solid rgba(98, 74, 48, 0.14);
color: var(--ink);
}

.primary-button {
color: white;
background: linear-gradient(135deg, #c97059, #af5847);
box-shadow: 0 14px 28px rgba(175, 88, 71, 0.24);
}
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 | 🟠 Major | ⚡ Quick win

Add a visible :focus-visible indicator for the custom buttons.

.rail-chip, .ghost-button, .primary-button (and .target-node at Line 294, .sheet-card at Line 412) all set border: 0 / background: none and the stylesheet never defines a :focus-visible rule, so the browser's default focus outline is also suppressed on most surfaces. Since the entire UI is keyboard-driven through these buttons (level rail, target picking, park/undo/restart, modal actions), keyboard users currently have no way to see which control has focus. This is an accessibility blocker for keyboard navigation.

♿ Suggested minimal focus-visible rule
+.rail-chip:focus-visible,
+.ghost-button:focus-visible,
+.primary-button:focus-visible,
+.sheet-card:focus-visible,
+.target-node:focus-visible {
+  outline: 3px solid var(--accent-dark);
+  outline-offset: 2px;
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.rail-chip,
.ghost-button,
.primary-button {
border: 0;
border-radius: 999px;
transition: transform 160ms ease, box-shadow 160ms ease, background-color 160ms ease;
}
.rail-chip {
min-height: 44px;
background: rgba(255, 251, 247, 0.84);
border: 1px solid rgba(98, 74, 48, 0.14);
color: var(--ink);
font-weight: 700;
}
.rail-chip.is-active,
.rail-chip:hover,
.ghost-button:hover,
.primary-button:hover,
.sheet-card:hover,
.target-node:hover {
transform: translateY(-1px);
}
.rail-chip.is-cleared {
box-shadow: inset 0 0 0 1px rgba(107, 150, 110, 0.36);
}
.rail-chip.is-locked {
opacity: 0.45;
}
.ghost-button,
.primary-button {
padding: 12px 18px;
font-weight: 700;
}
.ghost-button {
background: rgba(255, 251, 247, 0.86);
border: 1px solid rgba(98, 74, 48, 0.14);
color: var(--ink);
}
.primary-button {
color: white;
background: linear-gradient(135deg, #c97059, #af5847);
box-shadow: 0 14px 28px rgba(175, 88, 71, 0.24);
}
.rail-chip,
.ghost-button,
.primary-button {
border: 0;
border-radius: 999px;
transition: transform 160ms ease, box-shadow 160ms ease, background-color 160ms ease;
}
.rail-chip {
min-height: 44px;
background: rgba(255, 251, 247, 0.84);
border: 1px solid rgba(98, 74, 48, 0.14);
color: var(--ink);
font-weight: 700;
}
.rail-chip.is-active,
.rail-chip:hover,
.ghost-button:hover,
.primary-button:hover,
.sheet-card:hover,
.target-node:hover {
transform: translateY(-1px);
}
.rail-chip.is-cleared {
box-shadow: inset 0 0 0 1px rgba(107, 150, 110, 0.36);
}
.rail-chip.is-locked {
opacity: 0.45;
}
.ghost-button,
.primary-button {
padding: 12px 18px;
font-weight: 700;
}
.ghost-button {
background: rgba(255, 251, 247, 0.86);
border: 1px solid rgba(98, 74, 48, 0.14);
color: var(--ink);
}
.primary-button {
color: white;
background: linear-gradient(135deg, `#c97059`, `#af5847`);
box-shadow: 0 14px 28px rgba(175, 88, 71, 0.24);
}
.rail-chip:focus-visible,
.ghost-button:focus-visible,
.primary-button:focus-visible,
.sheet-card:focus-visible,
.target-node:focus-visible {
outline: 3px solid var(--accent-dark);
outline-offset: 2px;
}
🤖 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 `@sticker-studio/styles.css` around lines 214 - 263, Add a visible keyboard
focus indicator by defining :focus-visible styles for .rail-chip, .ghost-button,
.primary-button, .target-node, and .sheet-card (use the :focus-visible
pseudo-class so mouse focus is unaffected). Implement a high-contrast, rounded
indicator that respects existing border-radius (e.g., an outline or outer
box-shadow with a 2–4px thickness and accessible color from your palette) and
ensure it doesn't change layout (use outline or box-shadow, not borders). Apply
the rule to those selectors (e.g., ".rail-chip:focus-visible,
.ghost-button:focus-visible, .primary-button:focus-visible,
.target-node:focus-visible, .sheet-card:focus-visible") so keyboard users can
clearly see which control has focus.

@wuhuizuo wuhuizuo merged commit f1e514a into main May 6, 2026
4 checks passed
@wuhuizuo wuhuizuo deleted the agent/gameplay-engineer/705b616c branch May 6, 2026 09:03
github-actions Bot added a commit that referenced this pull request May 6, 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.

1 participant