Skip to content
Agentmart
Submit a tool

debug-menu-and-test-hooks

Gates test-only code behind one literal TEST_ONLY switch: debug menu, debug link, simulated clock and offline.

Skill by zakariaf

IndexedMIT★ 11
Write a review

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/zakariaf-debug-menu-and-test-hooks

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

Gates test-only code behind one literal TEST_ONLY switch: debug menu, debug link, simulated clock and offline. Use when adding a debug screen, a test hook, a debug link or a fake date for E2E or QA. Not for E2E flows (e2e-maestro).

Use cases: testing

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md and code. Checked Oct 26, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found
Runs as
Node.js package

Network hosts it talks to 1

  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/debug-menu-and-test-hooks

What's inside

Skill name
debug-menu-and-test-hooks
Description
Gates test-only code behind one literal TEST_ONLY switch: debug menu, debug link, simulated clock and offline. Use when adding a debug screen, a test hook, a debug link or a fake date for E2E or QA. Not for E2E flows (e2e-maestro).
License
MIT
Instructions
206 lines in SKILL.md · view on GitHub

Before installing

  • Ships executable scripts

Files (78)

  • SKILL.md
  • shared.json · assets/
  • create-root-kits.ts · examples/
  • debug-setup-flow.yaml · examples/
  • settings-target.ts · examples/
  • checker.md · references/
  • debug-deep-link.md · references/
  • gate-and-entry.md · references/
  • simulated-clock-and-connectivity.md · references/
  • wiring-and-menu.md · references/
  • check-lib.mjs · scripts/
  • check-test-hooks.mjs · scripts/
  • app-modules.mjs · scripts/lib/
  • hook-probes.mjs · scripts/lib/
  • source-scan.mjs · scripts/lib/
  • selftest.mjs · scripts/
  • debug-links.json · templates/contracts/
  • debug-links.schema.json · templates/contracts/
  • _layout.tsx · templates/src/app/(debug)/
  • index.tsx · templates/src/app/(debug)/debug/
  • setup.tsx · templates/src/app/(debug)/debug/
  • debug-links.test.ts · templates/src/features/debug/
  • debug-links.ts · templates/src/features/debug/
  • debug-menu-root.tsx · templates/src/features/debug/
  • debug-menu-screen.tsx · templates/src/features/debug/
  • debug-menu.test.tsx · templates/src/features/debug/
  • debug-motion-config.tsx · templates/src/features/debug/
  • debug-root-kit.test.tsx · templates/src/features/debug/
  • debug-root-kit.tsx · templates/src/features/debug/
  • debug-routes-production.test.tsx · templates/src/features/debug/
  • debug-routes.test.tsx · templates/src/features/debug/
  • debug-setup-plan.test.ts · templates/src/features/debug/
  • debug-setup-plan.ts · templates/src/features/debug/
  • debug-setup-root.tsx · templates/src/features/debug/
  • debug-setup-screen.tsx · templates/src/features/debug/
  • test-hooks-context.tsx · templates/src/features/debug/
  • test-hooks-root.tsx · templates/src/features/debug/
  • test-only-gate.test.ts · templates/src/features/debug/
  • use-debug-menu-model.test.tsx · templates/src/features/debug/
  • use-debug-menu-model.ts · templates/src/features/debug/
  • use-debug-setup-model.test.tsx · templates/src/features/debug/
  • use-debug-setup-model.ts · templates/src/features/debug/
  • ar.json · templates/src/i18n/catalogs/
  • en.json · templates/src/i18n/catalogs/
  • debug-storage-port.ts · templates/src/services/debug-storage/
  • fake-debug-storage.test.ts · templates/src/services/debug-storage/
  • fake-debug-storage.ts · templates/src/services/debug-storage/
  • kv-debug-storage-adapter.test.ts · templates/src/services/debug-storage/
  • kv-debug-storage-adapter.ts · templates/src/services/debug-storage/
  • create-test-hooks.test.ts · templates/src/services/test-hooks/
  • create-test-hooks.ts · templates/src/services/test-hooks/
  • debug-settings-target.ts · templates/src/services/test-hooks/
  • simulated-clock.test.ts · templates/src/services/test-hooks/
  • simulated-clock.ts · templates/src/services/test-hooks/
  • simulated-connectivity.test.ts · templates/src/services/test-hooks/
  • simulated-connectivity.ts · templates/src/services/test-hooks/
  • debug-overrides-reducer-cases.ts · templates/src/stores/debug-overrides/
  • debug-overrides-reducer.test.ts · templates/src/stores/debug-overrides/
  • debug-overrides-reducer.ts · templates/src/stores/debug-overrides/
  • debug-overrides-selectors.test.ts · templates/src/stores/debug-overrides/
  • …and 18 more

SKILL.md (first lines)

# Debug menu and test hooks

Development and preview builds carry a debug menu, one strict debug deep link and a simulated clock
and network that reach every reader, all through one literal gate; production builds contain none of
it, and a checker proves the gate, the entry, the wiring, the registry and the simulations on the
app's own code. Verified on 2026-10-10 with Expo SDK 57 (expo 57.0.26), React Native 0.86.3,
expo-router 57.0.24 and knip 6.38.0, in Jest and with back-to-back `expo export` bundles (iOS);
re-verified on 2026-10-17 wired through the root slots in the library's reference app, with
development and production exports for iOS and Android passing `check-variant-bundle`; not yet
driven on a simulator or an emulator.

## Rules that must hold

1. **One gate, one literal expression.** `src/test-only.ts` is the only way into test-only code:
   `export const TEST_ONLY: TestOnlyApi | null = process.env.EXPO_PUBLIC_APP_VARIANT === 'production'
   ? null : (require('./test-only-entry') as TestOnlyApi);` and nothing else runs there. Why: Metro
   folds only that literal comparison before it collects `require()` calls; an imported constant, a
   helper, `__DEV__` or `NODE_ENV` ships the code to the store or strips it from preview builds
   (app-variants-and-environment owns that rule). (`gate-file`, `gate-form`)
2. **`TestOnlyApi` is `typeof` the entry, never hand-written.** `import type * as TestOnlyEntry from
   './test-only-entry'`. Why: the gate casts the `require()`, so a hand-written type is never compared
   with the exports (`TEST_ONLY.x is not a function` in a preview build), and knip reported every
   entry export as unused with one (measured). (`api-derived`)
3. **The entry is a manifest with one unique sentinel.** Only `export { ... } from` lines and
   `TEST_BUILD_SENTINEL = '<APP>_TEST_BUILD_ONLY'`, a string no other app file contains. Why: the
   bundle and build checks prove the strip by that string, and a copy in shipped code fails every
   production proof. (`entry-manifest`, `sentinel`)
4. **Every export is read through `TEST_ONLY`, from declared test-only folders.** Each member is used
   as `TEST_ONLY?.member` by the startup, the root stack or a debug route, and each folder the entry
   re-exports from is in `contracts/architecture.json` `testOnlyFolders`. Why: an unread export is
   forgotten wiring, and an undeclared folder lets a static import ship it unnoticed.
   (`entry-reachable`, `test-only-folders`)
5. **The startup wraps the real ports once and hands only the wrapped services on.**
   `TEST_ONLY?.createTestHooks({ services: device, ... })` in the app registry's `prepareRootKits`
   (`src/services/create-root-kits.ts`), which AppRoot's `startApp` calls first, before the data layer
   opens, swaps `clock` and, when the services hold it, `connectivity` inside the services; every
   reader gets `testHooks?.services ?? device`. Why: one wrapper reaches every reader (due dates,
   repositories, TanStack Query through `services.connectivity`, banners, ads); a simulated port
   handed out beside the services leaves those readers on the real one. (`hooks-wired`, and the
   createTestHooks test)
6. **The simulated clock changes `today()` only.** It reads the simulated day on every call; `nowMs()`
   and `msUntilNextLocalDay()` call the real clock every time. Why: timers, timestamps and the
   next-day countdown must never jump, and `useToday()` still turns at the real midnight.
   (`simulated-clock`)
7. **Simulated offline is a full `ConnectivityPort` on the reachability hub.** `none` while
   simulating, the real reachability otherwise, `isOnline` derived from it, unchanged reports dropped,
   `stop()` releases the real adapter. Why: every consumer then follows the switch exactly as it
   follows a real outage. (`simulated-connectivity`)
8. **One debug deep link: an Expo Router route, one registry, one contract.**
   `<scheme>://debug/setup?...` is the route `src/app/(debug)/debug/setup.tsx`, which renders
   `TEST_ONLY?.DebugSetupRoot` with `useLocalSearchParams()` and redirects home in production (the
   canonical AppStack keeps the `(debug)` group behind `Stack.Protected` until the debug kit opens it);
   `DEBUG_LINK_PARAMS` and `contracts/debug-links.json` list the same parameters and values. Why:
   Expo Router delivers cold-start and running links once the navigator is ready, and flows read the
   contract, not the TypeScript. (`debug-registry`, `link-route`)
9. **A bad link refuses everything and says so.** An unknown, repeated or bad parameter returns a
   typed `debug-link-*` failure, applies nothing, is logged and shown as `debug.setup-error`. Why: a
   typo that is ignored makes a flow test the wrong state and pass. (`registry-strict`)
10. **Parse, plan, apply, replace.** The switches first, then saved settings (through the app's
    `DebugSettingsTarget`; refused when there is none), then `router.replace` to the screen; a
    direction restart ends the run. Every step sets a value, so a link applied twice changes nothing
    more. Why: reloads re-deliver the launch link and React's development effects run twice.
11. **The switches are one store, persisted apart from the app's data.** `src/stores/debug-overrides/`
    (reducer, cases, persist before publish) in `expo-sqlite/kv-store` under `debug.overrides`; the
    link and the menu write it through the same actions. Why: a reload or a kill keeps them, Maestro's
    `clearState` wipes them, and the menu always shows what a link set.
12. **Motion and probes follow the same switches, through the app's own seams.** The debug kit
    (`TEST_ONLY?.createDebugKit`, registered in the app's root-kit registry) fills the root's motion
    slot: `TestHooksRoot` takes `MotionConfig`'s place and passes `preference="on"` while
    `motion=still`; canvas probes
    render only while `probe=1`. Why: screenshots and E2E rest on the one reduce-motion path, never a
    second switch, and screenshots never show a probe.
13. **The strip is proven on the artifact.** After any change to the gate, the entry or variants,
    export development and production back to back without `--clear` and run
    app-variants-and-environment's `check-variant-bundle` on both. Why: only the bundle shows that the
    gate, the cache key and the variables worked together.

## Workflow

1. **Read** [references/gate-and-entry.md](references/gate-and-entry.md) (always), then for the task:
   the link [references/debug-deep-link.md](references/debug-deep-link.md); the clock, the network or
   the switches [references/simulated-clock-and-connectivity.md](references/simulated-clock-and-connectivity.md);
   wiring, the menu or another skill's hook [references/wiring-and-menu.md](references/wiring-and-menu.md);
   a checker line [references/checker.md](references/checker.md).
2. **Check the prerequisites**: the composition root and clock (architecture-and-boundaries), the
   `ConnectivityPort` and its hub (networking-and-server-state), `src/core/result.ts`, `date-key.ts`,
   `languages.ts`, the settings store if links change settings, and app-variants-and-environment's
   variants and Metro cache key.
3. **Copy the templates** to the paths their first lines name: `templates/src/test-only.ts` and
   `test-only-entry.ts` (rename `TASKS` in the sentinel), `templates/src/services/test-hooks/`,
   `templates/src/services/debug-storage/`, `templates/src/stores/debug-overrides/`,
   `templates/src/features/debug/` (the debug kit `debug-root-kit.tsx` included),
   `templates/src/app/(debug)/`, the `debug.*` keys of `templates/src/i18n/catalogs/`, and
   `templates/contracts/debug-links.json` with its schema. The test-hook kit (the gate, the hooks,
   the switches store, the storage, `TestHooksRoot` and the debug kit) is canonical in the library
   (its shared templates): the copies here are synced, never edited here.
4. **Declare the folders**: add `src/features/debug/`, `src/services/test-hooks/`,
   `src/services/debug-storage/` and `src/stores/debug-overrides/` to `contracts/architecture.json`
   `testOnlyFolders`; with a second persisting store, add state-management-zustand's
   `contracts/stores.json`.
5. **Wire it through the root slots** ([references/wiring-and-menu.md](references/wiring-and-menu.md)):
   two edits to the app's root-kit registry, never to the canonical root files
   ([examples/create-root-kits.ts](examples/create-root-kits.ts)): `prepareRootKits` makes the hooks
   and returns their services, and `createRootKits` registers `TEST_ONLY?.createDebugKit(hooks)` (the
   motion slot and the `(debug)` group); a settings target
   ([examples/settings-target.ts](examples/settings-target.ts)) once the app saves language and theme.
6. **Change a parameter or a switch test-first**: the registry test, the plan test, the reducer cases,
   then the code, then `contracts/debug-links.json`.
7. **Run the tests**: `npx jest src/features/debug src/services/test-hooks src/services/debug-storage src/stores/debug-overrides --ci --maxWorkers=2`
   and `npm run typecheck`, `npx eslint . --max-warnings 0`, `npx knip`.
8. **Prove the strip** (rule 13): the two exports, then app-variants-and-environment's
   `check-variant-bundle` on each; `rm -rf dist/variant-proof` after.
9. **Run the checker**: `node ${CLAUDE_SKILL_DIR}/scripts/check-test-hooks.mjs .`; fix each `FAIL`
   line (file, rule, fix) and rerun until `RESULT: PASS`.

## Definition of done

- [ ] `src/test-only.ts` is the template's expression with the derived `TestOnlyApi`; nothing else
      `require()`s code.

From zakariaf/ReactNative-Skills, MIT.

More from zakariaf/ReactNative-Skills

Signals

Stars
11
Forks
2
Open issues
0
Last commit (this folder)
Oct 17, 2026 (11 days ago)

From GitHub (fetched Oct 24, 2026).

Details

License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from zakariaf

Cite this page

debug-menu-and-test-hooks skill. Agent Mart. https://agentmart.dev/skills/zakariaf-debug-menu-and-test-hooks (last updated 2026-10-24)