agentleFS
fjrevoredofjrevoredo/mini-diarium/.agents/skills/tauri-agent-dev/SKILL.md

tauri-agent-dev

Skill318 starsChanged 13 days agoMITView on GitHub

Spawn, probe, and stop Mini Diarium's live Windows Tauri dev app with WebView2 CDP enabled, then hand control to agent-browser for real UI inspection. Use this whenever the user wants to manually test the real desktop UI, drive the dev app, verify a bug or preference in the actual window, inspect localStorage, take a real screenshot, or "actually try it in the app" instead of relying only on unit tests or WDIO. Also covers flows that open a native file/save dialog and async race tests (fake the dialog, hold a backend call, then navigate away) through IPC interception. Triggers: manually test the UI, drive the dev app, verify in the real UI, agent dev mode, spawn the dev app, open the running app and check, inspect the live Tauri window.

The file

Show its rules and text

We split this skill into the rules it gives an agent

32 rules. About 0 are common, 13 are distinctive, and 19 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈13

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 19

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Use this skill on Windows only.

    line 11
  2. Distinctive:

    Do not use it on macOS or Linux.

    line 12
  3. Distinctive:

    Run everything from the repo root with the Windows toolchain.

    line 16
  4. Very specific:

    Detecting readiness: don't grep the raw start output for guessed keywords ("ready", "listening", etc.) — per the note above, start never …

    line 64
  5. Distinctive:

    After start succeeds, connect the separate browser-driving layer:

    line 80
  6. Distinctive:

    If you changed the port, connect to that port instead.

    line 86
  7. Very specific:

    After agent-browser connect, use the normal browser-driving loop:

    line 90
  8. Very specific:

    use eval for DOM or localStorage reads

    line 95
  9. Very specific:

    Prefer the app's documented data-testid hooks where they exist.

    line 147
  10. Very specific:

    Don't step through the tour by hand and don't try to pre-seed its localStorage key — the key is onboarding-shown-{activeJournalId} …

    line 197
  11. Very specific:

    Auto-lock timer interference: If autoLockTimeout is short (< 30 s), the journal will lock between CDP roundtrips — eval calls do not …

    line 239
  12. Distinctive:

    Run this eval immediately after unlocking, before taking the first snapshot, and reload afterward if you used method 2.

    line 270
  13. Very specific:

    After scrollIntoView, take a screenshot to confirm the element is visible before clicking.

    line 297
  14. Very specific:

    When multiple UI interactions must happen without a roundtrip gap (e.g., to avoid the auto-lock timer or avoid stale refs), chain them in a …

    line 301
  15. Distinctive:

    Use this pattern when:

    line 325
  16. Very specific:

    Use this for any flow that opens a native OS dialog (file open/save, confirm from src/lib/dialog.ts) and for race tests ("the reply arrives …

    line 332
  17. Very specific:

    Verify before the first dialog click: installed: true, then do a harmless action that calls the backend (for example click a …

    line 383
  18. Distinctive:

    Put the delay before the real call to copy a slow backend step that runs before the write.

    line 387
  19. Distinctive:

    Create test files in the scratchpad directory, never in the repo.

    line 388
  20. Very specific:

    Read the outcome from window.__ipcLog plus a state eval (testids, [role=alert], disabled), taken at fixed points: before the context …

    line 389
  21. Distinctive:

    Use agent-browser's screenshot flow after the app is in the exact state the user cares about.

    line 406
  22. Distinctive:

    Always stop the dev session before finishing the task (PowerShell tool, see note in "Start A Session"):

    line 410
  23. Distinctive:

    Default mode is sandboxed.

    line 428
  24. Very specific:

    Start seeds config.json with a single sandbox journal on first run, so the app does not fall back to the journal picker.

    line 430
  25. Very specific:

    Use --use-real-config only when the bug depends on the user's actual app state.

    line 435
  26. Very specific:

    Start can take 30-90 seconds on a cold build.

    line 439
  27. Very specific:

    If port 9222 is already taken, restart with --port 9223 and connect agent-browser to that port.

    line 445
  28. Very specific:

    If start succeeds but the page target is not immediately recorded, run bun run agent:dev:probe (poll it — see "Detecting readiness" above).

    line 446
  29. Very specific:

    If probe says cdp unreachable, inspect .agent-dev/dev.log.

    line 447
  30. Distinctive:

    If probe says the managed PIDs are not alive, the dev session is gone.

    line 448
  31. Very specific:

    If a stop attempt fails, do not delete .agent-dev/state.json by hand until you understand which root is still alive.

    line 449
  32. Very specific:

    If agent:dev:stop fails with EBUSY while deleting a WebView cache file, rerun bun run agent:dev:stop -- --keep-sandbox.

    line 450

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Tauri Agent Dev

Platform Support

  • Use this skill on Windows only.
  • Do not use it on macOS or Linux. WebView2 CDP is the mechanism here; the other Tauri webviews do not match this flow.

Start A Session

Run everything from the repo root with the Windows toolchain.

Use the PowerShell tool, not Bash + cmd.exe, for every command in this skill. agent:dev:start, agent:dev:probe, agent:dev:stop, and agent-browser connect are exactly the kind of background-spawning / long-running commands where Bash piped through cmd.exe reliably returns only the cmd.exe banner with no real output — the same failure mode root CLAUDE.md calls out for website:build-static, but it is not specific to that one script. If a command run this way returns nothing but the banner, that is the signature of this issue, not a sign the command failed — retry it through the PowerShell tool before concluding anything is wrong.

agent:dev:start does not return to you — always launch it with run_in_background: true. The script itself is written to exit once CDP is up (it spawns tauri dev, polls, prints Tauri dev running., and falls off the end of main()), but through this tool chain the call stays attached to the live process tree and is only reported as complete when you later run agent:dev:stop. Waiting on it in the foreground burns the entire tool timeout and teaches you nothing: a foreground call with a 400 s timeout will be force-backgrounded at 400 s even though the app window opened ~60 s in. Launch it backgrounded and use probe as the readiness signal:

# 1. Launch — run_in_background: true, do NOT wait on this call
bun run agent:dev:start

# 2. Readiness — this one DOES return, in well under a second
bun run agent:dev:probe

The same applies to agent-browser connect <port>: it does not return either. Launch it backgrounded (or simply issue it and move on) and treat the first successful agent-browser snapshot as proof the connection is live — snapshot returns normally.

Useful flags:

bun run agent:dev:start -- --port 9223
bun run agent:dev:start -- --timeout 180
bun run agent:dev:start -- --use-real-config

What start does:

  • launches tauri dev
  • enables WebView2 remote debugging
  • defaults to a sandbox under .agent-dev/sandbox/
  • seeds .agent-dev/sandbox/app/config.json on first run so the frontend auto-selects the sandbox journal
  • isolates WebView storage under .agent-dev/sandbox/webview/ so localStorage does not leak across runs
  • writes runtime state to .agent-dev/state.json
  • writes logs to .agent-dev/dev.log

Detecting readiness: don't grep the raw start output for guessed keywords ("ready", "listening", etc.) — per the note above, start never hands its output back, so its stdout is not a readiness signal at all through this tool chain. agent:dev:probe is the purpose-built readiness check and returns structured JSON ({"running":true,...}) the moment the CDP target is live. Poll it directly instead of building an ad hoc log-watcher:

# Poll probe every few seconds until it reports running, instead of waiting on a fixed
# timer or grepping dev.log for inferred markers.
until bun run agent:dev:probe 2>&1 | grep -q '"running":true'; do sleep 3; done

Cold builds take 30-90 seconds; pass --timeout 180 if a Rust rebuild is expected. If the app window is already visibly open (the user can see it), trust that over a probe/log timeout — the window appearing means the session is up even if a polling loop hasn't caught up yet.

After start succeeds, connect the separate browser-driving layer:

agent-browser connect 9222

If you changed the port, connect to that port instead.

Drive The UI

After agent-browser connect, use the normal browser-driving loop:

  1. agent-browser snapshot
  2. click or fill controls
  3. re-snapshot after meaningful UI transitions
  4. use eval for DOM or localStorage reads
  5. take screenshots when the user needs proof

PowerShell notes:

  • Quote @eNNN refs. Use agent-browser click '@e5', not agent-browser click @e5.
  • Pass every non-trivial eval script as a single-quoted here-string, never as an inline double-quoted argument. PowerShell eats backslash-escaped quotes inside a "…" argument, so JS containing quoted selectors arrives at the browser mangled. This call:

    agent-browser eval "(function(){ document.querySelectorAll('a[aria-label^=\"Open entry\"]') })()"

    fails with 'a[aria-label^= Open entry]' is not a valid selector — the inner quotes were stripped, not escaped. The failure looks like a bad selector, so it sends you rewriting correct JS. Use:

    $js = @'
    (function(){
      const rows = Array.from(document.querySelectorAll('li button'))
        .filter(b => (b.getAttribute('aria-label') || '').startsWith('Open entry'));
      return { count: rows.length };
    })()
    '@
    agent-browser eval $js

    The @'…'@ form is literal — no $, backtick, or quote interpretation — so the JS reaches the browser byte-for-byte. The closing '@ must be at column 0 on its own line. As a bonus, startsWith/includes filtering in JS avoids CSS attribute-selector quoting entirely.

Stale ref warning: @eNNN refs are assigned at snapshot time. Any DOM mutation (tab switch, scroll, dialog open/close) can reassign refs so an old ref silently targets a different element. For controls inside scrollable panels or dialogs, prefer CSS selectors or JS eval with label-text matching over bare @eNNN refs. Always re-snapshot after a meaningful transition before clicking a ref from a previous snapshot.

type requires the selector and text in the same call: agent-browser type <sel> <text> takes both arguments together — e.g. agent-browser type '@e15' 'some text'. Calling click '@e15' and then type with only the text string (no selector) is a silent no-op: it returns success but nothing is typed, and there is no error to signal the mistake. If eval'd editor/input content comes back empty after a type call, check this first before assuming a focus or timing problem.

Don't burn a full wakeup/turn-cycle on a sub-second wait: known short timers (e.g. the 500ms autosave debounce) don't need ScheduleWakeup or a minute-long pause — that wastes a conversation turn per check. Use a short shell-level wait (sleep 1-2 inline before the next command, or a tight Monitor poll loop) so the verification stays in the same turn.

Stable Selectors

Prefer the app's documented data-testid hooks where they exist. Do not invent new ones.

The canonical list is the data-testid table in src/CLAUDE.md — read it there rather than trusting a copy. It is maintained as E2E contract and grows whenever a new testid ships; a duplicated list here goes stale silently and sends you hunting by text for controls that already have a stable hook. The ones you reach for most in this workflow:

Purposetestid
Journal creationpassword-create-input, password-repeat-input, create-journal-button
Journal unlockpassword-unlock-input, unlock-journal-button
Headertoggle-sidebar-button, lock-journal-button, search-button, timeline-toggle-button, header-date-title, header-prev-day-button, header-next-day-button
Overflow menuheader-more-menu-trigger, then header-more-menu-{preferences,statistics,import,export}-item
Overlays (assert open)preferences-overlay, stats-overlay, import-overlay, export-overlay, search-overlay
Onboardingonboarding-next-btn
Editortitle-input, entry-nav-bar, entry-{prev,next,add,delete,lock}-button, entry-number-button-{N}
Calendarcalendar-day-YYYY-MM-DD

Preferences has no data-testids, but it does have stable id attributes — use those, not visible text (text matching breaks under a non-English language preference):

  • Tabs: #pref-tab-general, #pref-tab-writing, #pref-tab-security, #pref-tab-data, #pref-tab-advanced. Note writing and security render only while unlocked (PreferencesOverlay.tsx), so unlock before reaching for them.
  • Controls: e.g. #pref-first-day, #pref-timeline-date-format, #show-timeline-preview, #hide-titles, #allow-future, #enable-spellcheck, #editor-font-size.

Common Recipes

Create Or Unlock A Journal

For a fresh sandbox:

  1. fill password-create-input
  2. fill password-repeat-input
  3. click create-journal-button

For an existing sandbox journal:

  1. fill password-unlock-input
  2. click unlock-journal-button

Dismiss The Onboarding Tour (do this immediately after creating a journal)

A fresh sandbox always lands in the onboarding tour, and its overlay swallows clicks aimed at the header. This is not an occasional annoyance — it fires on every first run, and the symptom is misleading: agent-browser click '[data-testid=header-more-menu-trigger]' reports ✓ Done, the menu never opens, and the follow-up click on a menu item fails with Element not found. It reads like a broken menu or a stale ref. Check for onboarding-next-btn in the DOM before believing that.

Don't step through the tour by hand and don't try to pre-seed its localStorage key — the key is onboarding-shown-{activeJournalId} (src/state/onboarding.ts), and the journal id does not exist until after creation. Click through to the end in one eval instead; the loop is id-agnostic and survives the tour gaining or losing steps:

$js = @'
(function(){
  let clicks = 0;
  for (let i = 0; i < 12; i++) {
    const b = document.querySelector('[data-testid=onboarding-next-btn]');
    if (!b) break;
    b.click(); clicks++;
  }
  return { clicks, stillThere: !!document.querySelector('[data-testid=onboarding-next-btn]') };
})()
'@
agent-browser eval $js

Expect stillThere: false. The final click calls dismissOnboarding(), which persists the key, so the tour stays gone for the rest of the sandbox's life — including across reloads.

Read Or Verify Preferences

Open Preferences via header-more-menu-trigger → header-more-menu-preferences-item (dismiss the onboarding tour first — see above, or the trigger click silently does nothing), then click the tab by id (#pref-tab-writing, etc.).

To inspect saved preferences directly:

JSON.parse(localStorage.getItem('preferences') ?? '{}');

Typical checks:

  • autoLockEnabled
  • autoLockTimeout
  • language
  • editorFontFamily

Auto-lock timer interference: If autoLockTimeout is short (< 30 s), the journal will lock between CDP roundtrips — eval calls do not dispatch DOM activity events and therefore do not reset the idle timer.

A bare localStorage.setItem('preferences', ...) write does not fix this — confirmed empirically, not just by reading the source. preferences (src/state/preferences.ts) is a Solid signal created once at module load (createSignal(loadPreferences())); the running app never re-reads localStorage on its own, so writing to it directly changes only what a future page load will pick up, not the live in-memory value the idle timer effect (App.tsx) actually reads. A session that patches localStorage this way and keeps driving the UI will still hit the original timeout and lock mid-session, which looks like a flaky app rather than a stale-write bug.

Two ways to actually change the live value:

  1. Drive the real Preferences → Security UI (reliable, no reload needed): open Preferences, click #pref-tab-security, toggle the "Lock after inactivity" checkbox and/or set the timeout input via the native-setter + input/blur-event pattern (see "Compound Eval for Atomic UI Chains"). This calls the app's own setPreferences(), so the live signal updates immediately.
  2. Write localStorage then reload the page (agent-browser reload or eval location.reload()): the fresh module load re-reads the patched value. Necessary when the app is on the lock screen and the Security tab isn't reachable yet.
// Extend timeout so the journal stays unlocked during the session — pairs with either method above
(function() {
  const p = JSON.parse(localStorage.getItem('preferences') ?? '{}');
  p.autoLockTimeout = 600;
  localStorage.setItem('preferences', JSON.stringify(p));
})();

Run this eval immediately after unlocking, before taking the first snapshot, and reload afterward if you used method 2. Restore the original value when done if needed.

Navigate A Scrollable Preferences Dialog

The Preferences dialog clips its content with a scrollable container — not the [role="tabpanel"] element itself (which has overflow: visible). The actual scroller has class .flex-1.overflow-y-auto.

Correct approach — scroll a specific element into view:

// Works: scrolls a target element into the visible area
document.querySelector('#some-element-id').scrollIntoView({behavior: 'instant', block: 'center'});

// Or find by label text:
Array.from(document.querySelectorAll('input[type="checkbox"]'))
  .find(el => el.labels?.[0]?.textContent?.trim() === 'Lock after inactivity')
  ?.scrollIntoView({behavior: 'instant', block: 'center'});

Wrong approach — setting scrollTop on the tabpanel:

// Does NOT work: the tabpanel has overflow:visible
document.querySelector('[role="tabpanel"]').scrollTop = 400;

After scrollIntoView, take a screenshot to confirm the element is visible before clicking.

Compound Eval for Atomic UI Chains

When multiple UI interactions must happen without a roundtrip gap (e.g., to avoid the auto-lock timer or avoid stale refs), chain them in a single eval call:

(function() {
  // 1. Open a dialog
  const openBtn = document.querySelector('[data-testid=header-more-menu-preferences-item]');
  if (!openBtn) return {error: 'menu item not found — is the overflow menu open?'};
  openBtn.click();

  // 2. Navigate to a tab (by id — text matching breaks under a non-English language pref)
  const tab = document.querySelector('#pref-tab-security');
  if (!tab) return {error: 'tab not found — unlocked?'};
  tab.click();

  // 3. Read a UI control state
  const cb = Array.from(document.querySelectorAll('input[type="checkbox"]'))
    .find(el => el.labels?.[0]?.textContent?.trim() === 'Lock after inactivity');
  if (!cb) return {error: 'checkbox not found'};
  cb.scrollIntoView({block: 'center'});
  return {checkboxChecked: cb.checked};
})()

Use this pattern when:

  • The idle timer is short and would fire between steps
  • You need to read state immediately after opening a dialog (before refs can go stale)
  • You're verifying that a setting persisted after Save + re-open in one shot

Fake Native Dialogs And Slow Backend Calls (IPC Interception)

Use this for any flow that opens a native OS dialog (file open/save, confirm from src/lib/dialog.ts) and for race tests ("the reply arrives after the user navigated away").

Never click a control that opens a native dialog until the interceptor below is installed and verified. CDP cannot see or drive a native dialog. The click opens a real OS window that stays open until the user closes it by hand, and the app's await hangs until then.

Intercept window.fetch, not __TAURI_INTERNALS__.invoke. invoke (and ipc) are non-writable, non-configurable properties: an assignment to them fails silently in non-strict eval code, so the "wrapped" check passes while the real invoke still runs. Every IPC call goes through fetch('http://ipc.localhost/<command>', { body: JSON args }), and fetch is writable. Plugin commands are URL-encoded in the path (plugin:dialog|open → plugin%3Adialog%7Copen). A fake reply needs the Tauri-Response: ok header (or error) and a JSON content type.

$js = @'
(function(){
  if (!window.__origFetch) window.__origFetch = window.fetch.bind(window);
  window.__ipcLog = [];
  window.__ipcSeen = 0; // every IPC call, to prove the wrapper is on the real path
  // Edit these per test. FAKES: command -> reply. DELAYS: command -> ms before the real call.
  const FAKES = { 'plugin:dialog|open': ['C:\\path\\to\\scratchpad\\test-file.pdf'] };
  const DELAYS = { 'add_entry_attachment': 6000 };
  window.fetch = async (input, init) => {
    const url = String(input?.url ?? input);
    if (!url.startsWith('http://ipc.localhost/')) return window.__origFetch(input, init);
    const cmd = decodeURIComponent(url.slice('http://ipc.localhost/'.length));
    window.__ipcSeen++;
    if (cmd in FAKES) {
      window.__ipcLog.push(`${cmd}: faked`);
      return new Response(JSON.stringify(FAKES[cmd]),
        { headers: { 'Content-Type': 'application/json', 'Tauri-Response': 'ok' } });
    }
    if (cmd in DELAYS) {
      window.__ipcLog.push(`${cmd}: held ${DELAYS[cmd]} ms, args ${init?.body}`);
      await new Promise(r => setTimeout(r, DELAYS[cmd]));
      const res = await window.__origFetch(input, init);
      window.__ipcLog.push(`${cmd}: ${res.headers.get('Tauri-Response')} ${(await res.clone().text()).slice(0, 150)}`);
      return res;
    }
    return window.__origFetch(input, init);
  };
  return { installed: window.fetch !== window.__origFetch };
})()
'@
agent-browser eval $js

Rules:

  • Verify before the first dialog click: installed: true, then do a harmless action that calls the backend (for example click a calendar-day-*) and check window.__ipcSeen > 0. A page reload (HMR, location.reload()) removes the wrapper; reinstall after any reload.
  • A delayed command still runs on the real backend, so a race test gets the real error text. Put the delay before the real call to copy a slow backend step that runs before the write.
  • Create test files in the scratchpad directory, never in the repo.
  • Read the outcome from window.__ipcLog plus a state eval (testids, [role=alert], disabled), taken at fixed points: before the context change, right after it, and after the held call settles.

Editor facts that shape race tests:

  • A day has no entry (and no tag/attachment strip) until the first keystroke in the title or body. Type one character to create the entry.
  • Leaving an entry whose saved content would be lost opens the in-app leave dialog (confirm-dialog, "Delete entry"). Navigation waits for it. Use confirm-dialog-confirm-button to delete the entry during a held call, or confirm-dialog-cancel-button to stay. Check for confirm-dialog after each navigation click.
  • For the "target" side of a switch, give the other day an entry first; otherwise the strip unmounts and there is nothing to observe.

Capture A Screenshot

Use agent-browser's screenshot flow after the app is in the exact state the user cares about. Prefer this over describing the UI from memory.

End The Session

Always stop the dev session before finishing the task (PowerShell tool, see note in "Start A Session"):

bun run agent:dev:stop

Optional:

bun run agent:dev:stop -- --keep-sandbox

Stopping is not optional cleanup. It kills both long-lived Windows process roots and removes sandbox state unless told otherwise.

If agent:dev:stop fails during sandbox deletion with a transient WebView file lock (EBUSY on a file under .agent-dev/sandbox/webview/EBWebView/...), immediately retry with --keep-sandbox. Treat that as the normal fallback: the important part is stopping the managed processes and closing the CDP port, not forcing one last WebView cache file to be deleted in the same step.

Sandbox Semantics

  • Default mode is sandboxed.
  • Sandbox paths live under .agent-dev/sandbox/.
  • Start seeds config.json with a single sandbox journal on first run, so the app does not fall back to the journal picker.
  • MINI_DIARIUM_DATA_DIR points directly at the sandbox diary dir, while the seeded app config gives the frontend an active journal selection.
  • WebView storage is isolated under the sandbox too, so preferences and other localStorage state start clean on a fresh sandbox.
  • First launch against an empty sandbox lands on journal creation.
  • Reusing the same sandbox lands on password unlock.
  • Use --use-real-config only when the bug depends on the user's actual app state.

Troubleshooting

  • Start can take 30-90 seconds on a cold build. Use --timeout 180 if Rust rebuilds are expected.
  • agent:dev:start or agent-browser connect "times out" / gets force-backgrounded: expected — neither returns through this tool chain. Launch both with run_in_background: true and poll bun run agent:dev:probe for readiness. See "Start A Session".
  • Clicks report ✓ Done but nothing happens, then the next selector is Element not found: on a fresh sandbox this is the onboarding tour overlay intercepting the click, not a broken control. Check for [data-testid=onboarding-next-btn] and dismiss it — see "Dismiss The Onboarding Tour".
  • eval fails with '…' is not a valid selector on JS you know is correct: PowerShell stripped the escaped inner quotes. Re-send the script as a @'…'@ here-string — see the PowerShell notes under "Drive The UI".
  • A wrapper around __TAURI_INTERNALS__.invoke "installs" but sees no calls: invoke is non-writable, so the assignment failed silently. Wrap window.fetch instead — see "Fake Native Dialogs And Slow Backend Calls".
  • A click hangs the flow and the user reports an open OS file dialog: a native dialog opened without a fake. Ask the user to click Cancel (the app treats it as "nothing picked"), then install the IPC interceptor before you click again.
  • If port 9222 is already taken, restart with --port 9223 and connect agent-browser to that port.
  • If start succeeds but the page target is not immediately recorded, run bun run agent:dev:probe (poll it — see "Detecting readiness" above). Probe resolves the current page target from the live /json list.
  • If probe says cdp unreachable, inspect .agent-dev/dev.log.
  • If probe says the managed PIDs are not alive, the dev session is gone. Start a new one.
  • If a stop attempt fails, do not delete .agent-dev/state.json by hand until you understand which root is still alive.
  • If agent:dev:stop fails with EBUSY while deleting a WebView cache file, rerun bun run agent:dev:stop -- --keep-sandbox. Verify that the reported port is closed; if it is, the session shutdown is good enough for task cleanup.
  • Journal locks repeatedly during session: If the app is configured with a short autoLockTimeout (< 30 s), the idle timer fires between CDP roundtrips. eval calls do not count as user activity. A bare localStorage patch does not fix this on its own — it only changes what a future page load reads, not the live in-memory value the idle timer effect actually uses. Drive the real Preferences → Security UI to change it live, or patch localStorage and then reload — see "Auto-lock timer interference" under "Read Or Verify Preferences" above.
  • Dev server reports ready but the page never loads past "Loading Mini Diarium..." / agent-browser snapshot returns (empty page) forever: seen twice — Vite's dependency optimizer can wedge itself into a state where it logs ready in NNN ms and accepts the TCP connection (Get-NetTCPConnection -LocalPort 1420 shows Listen/Established), but never actually completes an HTTP response for /src/index.tsx or any other module — curl/Invoke-WebRequest to http://localhost:1420/ hang and time out even though the port is open. This is a stale node_modules/.vite optimizer cache, not a CDP/agent-browser problem or an app bug — confirm with a direct request to the dev server port before assuming the browser side is at fault. Fix: bun run agent:dev:stop, delete node_modules/.vite, then bun run agent:dev:start again. The next start takes longer (full dependency re-bundle) but comes up clean.

What This Skill Does Not Do

  • It does not replace WDIO or CI E2E coverage.
  • It does not support production builds.
  • It does not support macOS or Linux.
  • It does not bundle browser automation itself; it relies on the separate agent-browser capability after startup.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.