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
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.
About
Use cases: testing
Reviews
Write a reviewNo 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
- 0.606score
accessibility-as-code
zakariaf
Enforces a11y labels, 44pt targets, 200% text, VoiceOver, TalkBack.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
adaptive-layout
zakariaf
Lays screens out by window, not device: breakpoints, master-detail panes, readable widths, grids.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
admob-ads-and-consent
zakariaf
Builds AdMob ads behind a port - banners, interstitials, rewarded, UMP consent then ATT, pacing, ADS_MODE test IDs.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
analytics-and-consent
zakariaf
Gates analytics on opt-in consent: no SDK before a yes, typed events without personal data, ATT.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
android-release-play
zakariaf
Ships Android App Bundles to Google Play: upload key, Play App Signing, AAB gate, versionCode, tracks, rollout.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
architecture-and-boundaries
zakariaf
Places app code in the src/ layout and checks import boundaries: one-way zones, pure core, ports with fakes, one composition root.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
async-safety
zakariaf
Prevents silent async bugs: effect races, stale results, lost promises, leaked listeners and timers, AbortSignal gaps.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
audio-and-haptics-feedback
zakariaf
Builds SFX and haptics behind ports.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
auth-and-secure-storage
zakariaf
Builds sign-in - tokens in expo-secure-store, shared refresh, protected routes, OAuth PKCE, Apple sign-in.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
background-tasks
zakariaf
Builds expo-background-task jobs: idempotent, budgeted runs.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
ci-pipeline-and-gates
zakariaf
Builds GitHub Actions CI for Expo apps - verify on every PR, SHA-pinned actions, pinned runners and Node, device lanes, manual EAS releases.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
canvas-gestures-and-hit-testing
zakariaf
Turns canvas taps, swipes, drags and aim into typed intents: one gesture module, hit-testing on the drawn layout.
SkillAFreshness: A★ 11Claude Code +9
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
- Repository
- github.com/zakariaf/ReactNative-Skills
- First seen
- Oct 24, 2026
- Last checked
- Oct 24, 2026
- Source
- GitHubgithub-graphql
More from zakariaf
- 0.606score
admob-ads-and-consent
zakariaf
Builds AdMob ads behind a port - banners, interstitials, rewarded, UMP consent then ATT, pacing, ADS_MODE test IDs.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
analytics-and-consent
zakariaf
Gates analytics on opt-in consent: no SDK before a yes, typed events without personal data, ATT.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
canvas-gestures-and-hit-testing
zakariaf
Turns canvas taps, swipes, drags and aim into typed intents: one gesture module, hit-testing on the drawn layout.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
accessibility-as-code
zakariaf
Enforces a11y labels, 44pt targets, 200% text, VoiceOver, TalkBack.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
adaptive-layout
zakariaf
Lays screens out by window, not device: breakpoints, master-detail panes, readable widths, grids.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
android-release-play
zakariaf
Ships Android App Bundles to Google Play: upload key, Play App Signing, AAB gate, versionCode, tracks, rollout.
SkillAFreshness: A★ 11Claude Code +9
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)