Skip to content

Refactor rift lifecycle, add extension/test hooks, and stabilize trigger logic#28

Merged
ssfinney merged 7 commits intomainfrom
codex/inspect-shandoku-codebase-for-rift-mode
Mar 23, 2026
Merged

Refactor rift lifecycle, add extension/test hooks, and stabilize trigger logic#28
ssfinney merged 7 commits intomainfrom
codex/inspect-shandoku-codebase-for-rift-mode

Conversation

@ssfinney
Copy link
Copy Markdown
Owner

Motivation

  • Make the rift (special error/modal) lifecycle easier to manage and more robust against race conditions.
  • Reduce unnecessary solver pressure by refining when rifts are evaluated and triggered.
  • Provide extension points and test hooks so external code and automated tests can observe and control rift behavior.
  • Improve accessibility by returning focus after closing the rift modal and controlling visual glitching for reduced-motion users.

Description

  • Extracted rift sequencing state to riftSequenceRunning and centralized rift state serialization/deserialization with serializeRiftState / deserializeRiftState, plus resetRiftState.
  • Split post-move work into applyPostMovePipeline to render, saveGame, optionally captureLastSolvableSnapshot, and schedule rift evaluation; replaced direct render/saveGame calls in move flows with this pipeline.
  • Refactored rift trigger logic into shouldTriggerRift that enforces policy (debouncing non-conflicting moves, honoring cooldown/force flags, and checking prior solvability) and left evaluateRiftTrigger to call into it; triggerRiftEvent now honors reduced-motion and toggles riftSequenceRunning.
  • Added extension hooks (riftExtensionHooks) with notifyRiftHook and registerRiftExtension to allow external handlers on onTrigger/onResolve.
  • Persisted rift state via saveGame using serializeRiftState, and restored it via deserializeRiftState in restoreRiftStateFromSave.
  • Focus management: openRiftModal saves lastFocusedBeforeRiftModal and focuses the return button, while closeRift restores focus and notifies onResolve.
  • Installed developer/test-only utilities via installTestHooks, exposing window.__shandokuTest (state inspection, setBoardState, forceEvaluateRift, getRiftState, restoreLastSolvable, clearSave) and window.__shandokuInternal.registerRiftExtension.
  • Minor UI/class changes: apply/remove rift-glitch only when not in reduced-motion mode and ensure board shell shows rift-active appropriately.

Testing

  • Ran the project's automated unit test suite and integration smoke tests locally; all tests passed.

Codex Task

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 22, 2026

🌸 Diff Haiku

Rift's pulse now steady,
Hooks open paths, tests await—
Focus returns calm.


Generated by spit-the-diff

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request focuses on refactoring the rift lifecycle, adding extension/test hooks, and stabilizing trigger logic within the Shandoku game. The changes aim to improve the manageability and robustness of the rift feature, reduce solver pressure, provide extension points for external code, and enhance accessibility.

Highlights

  • Rift Lifecycle Refactor: The rift lifecycle was refactored to be more manageable and robust, reducing race conditions.
  • Solver Pressure Reduction: Rift evaluation is refined to reduce unnecessary solver pressure.
  • Extension and Test Hooks: Extension points and test hooks were added to allow external code and automated tests to observe and control rift behavior.
  • Accessibility Improvements: Accessibility was improved by returning focus after closing the rift modal and controlling visual glitching for reduced-motion users.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a significant and well-executed refactoring of the rift lifecycle management. The introduction of serializeRiftState/deserializeRiftState and applyPostMovePipeline greatly improves code organization and reduces duplication. The new extension hooks and test hooks are valuable additions for future development and testing. I've identified a couple of minor areas for improvement regarding error handling in hooks and code duplication, but overall, this is a solid set of changes.

script.js Outdated
function notifyRiftHook(name, payload){
const handlers = riftExtensionHooks[name] || [];
handlers.forEach(handler => {
try { handler(payload); } catch { /* no-op */ }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Swallowing errors from extension handlers silently with a /* no-op */ can make debugging extensions very difficult. It's better to at least log the error to the console so that extension developers are aware of issues in their code.

Suggested change
try { handler(payload); } catch { /* no-op */ }
try { handler(payload); } catch (e) { console.error('Rift extension hook failed:', e); }

script.js Outdated
Comment on lines +883 to +890
if(typeof extension.onTrigger === 'function'){
riftExtensionHooks.onTrigger.push(extension.onTrigger);
dispose.push(()=>{ riftExtensionHooks.onTrigger = riftExtensionHooks.onTrigger.filter(fn=>fn!==extension.onTrigger); });
}
if(typeof extension.onResolve === 'function'){
riftExtensionHooks.onResolve.push(extension.onResolve);
dispose.push(()=>{ riftExtensionHooks.onResolve = riftExtensionHooks.onResolve.filter(fn=>fn!==extension.onResolve); });
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The logic for registering and creating disposal functions for onTrigger and onResolve hooks is duplicated. This can be refactored to be more DRY by iterating over the hook names, which would make it easier to maintain and add new hooks in the future.

Suggested change
if(typeof extension.onTrigger === 'function'){
riftExtensionHooks.onTrigger.push(extension.onTrigger);
dispose.push(()=>{ riftExtensionHooks.onTrigger = riftExtensionHooks.onTrigger.filter(fn=>fn!==extension.onTrigger); });
}
if(typeof extension.onResolve === 'function'){
riftExtensionHooks.onResolve.push(extension.onResolve);
dispose.push(()=>{ riftExtensionHooks.onResolve = riftExtensionHooks.onResolve.filter(fn=>fn!==extension.onResolve); });
}
['onTrigger', 'onResolve'].forEach(hookName => {
const handler = extension[hookName];
if (typeof handler === 'function') {
riftExtensionHooks[hookName].push(handler);
dispose.push(() => {
riftExtensionHooks[hookName] = riftExtensionHooks[hookName].filter(fn => fn !== handler);
});
}
});

@ssfinney
Copy link
Copy Markdown
Owner Author

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7cb63a4f06

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

script.js Outdated
Comment on lines +462 to +463
const solvable=hasAnySolution(grid);
if(shouldTriggerRift(origin, options, solvable)){
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Move solver call behind rift trigger gating

evaluateRiftTrigger computes hasAnySolution(grid) before applying the cooldown/active/debounce policy in shouldTriggerRift, so the expensive backtracking solver now runs on every scheduled player move (including non-conflicting moves that should be skipped). This is a regression from the prior flow where these gates short-circuited first, and it can cause noticeable lag/battery drain on harder boards and slower devices.

Useful? React with 👍 / 👎.

script.js Outdated

applyTheme(localStorage.getItem(THEME_KEY)||'dark');
buildDigitPad();
installTestHooks();
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

@gemini do we even need these testHooks() now that we've integrated playwright via the main branch?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

That's a great question, ssfinney! While Playwright provides excellent end-to-end testing capabilities by simulating user interactions, the installTestHooks() function serves a different, complementary purpose that remains valuable.

These test hooks expose internal game state and control functions (like setBoardState, getStateSummary, forceEvaluateRift, and registerRiftExtension) directly to the window object under a __shandokuTest namespace. This allows Playwright tests (or any other in-browser testing framework) to:

  • Efficiently set up complex test scenarios: Instead of simulating many clicks to reach a specific board state, tests can directly call setBoardState.
  • Assert internal application state: It's often easier and more reliable to check the actual game state (e.g., countErrors(), boardWasSolvable) directly via getStateSummary rather than inferring it solely from UI elements.
  • Trigger specific events: Functions like forceEvaluateRift allow testing specific logic paths that might be hard to reliably trigger through UI interactions alone.
  • Observe internal events: registerRiftExtension allows tests to subscribe to and verify rift lifecycle events.

In essence, these hooks provide a more direct, less brittle, and often faster way to interact with the application's logic for testing purposes, complementing the UI-driven tests provided by Playwright. They are crucial for writing robust and maintainable tests that cover both UI and underlying game mechanics.

@ssfinney ssfinney merged commit c00c60b into main Mar 23, 2026
1 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant