Commit Graph
28 Commits
Author SHA1 Message Date
Tyler 962f929de2 fixed recording workflow on ubuntu
ai gen description: Root cause 1 — The mandatory constraint removal (most critical)

In Electron 29+ / Chromium 116+, the old getUserMedia format with a mandatory: {} wrapper was removed. The capture worker was still using it:

video: { mandatory: { chromeMediaSource: 'desktop', ... } }  // broke in Electron 29+
This caused getUserMedia to throw in the worker, which sent a stream-error back to the main process, which fell back to the frame loop. Fix: flat constraint format with no mandatory wrapper.

Root cause 2 — Frame loop spammed getSources() every 200ms

Once the stream backend failed, the fallback frame loop called desktopCapturer.getSources() 5× per second. On Linux via the XDG portal, each getSources() call is a new permission request → dialog every few seconds. Fix: on Linux without click capture, don't fall back to the frame loop at all — just let interval/hotkey captures take individual fresh shots.

Root cause 3 — Interval/hotkey captures bypassed the stream backend

Even when the stream backend is running, sessionCapture('interval') went straight to shoot() → grab() → getSources(). Fix: non-click triggers now pull a buffered frame from the stream backend's ring buffer (sampled every 100ms), completely avoiding getSources().

Root cause 4 — Stream backend never started on Wayland

The earlier fix made recorderWanted = false on Wayland, so the stream backend never even started. Fix: recorderWanted is now true whenever stream capture is enabled (the default), regardless of whether click detection is available.

Safety net — setPermissionCheckHandler

Electron 29+ requires an explicit permission grant for display-capture in renderer windows. Added a handler that grants all permissions (safe for a fully local/offline app like StepForge).

Windows is completely unaffected — the constraint fix is Electron-version level (affects both platforms the same), and all the Linux-specific frame-loop guards only fire when clickCaptureAvailable() returns false, which only happens on Wayland.
2026-06-26 16:11:16 -05:00
TylerandClaude Sonnet 4.6 d244626583 ai smart auto-documentation and text rewrite
- Store captureMetadata (OCR text, window/app/element info) with each
  step at capture time so AI always has the original rich context.
- Add buildCaptureContext() to TextIntelService; capture.js uses it
  instead of buildCaptureTitle() so both title and metadata come from
  one pass.
- generateStepPatch() prefers stored captureMetadata over re-running
  OCR, giving the AI the best possible context when the user clicks
  an AI button later.
- Add autoDoc setting: when enabled every capture (shoot, region, and
  session hotkey/click) is automatically documented by AI. Manual
  captures await AI before returning; session captures fire-and-forget
  and push a step:updated event so the renderer reloads seamlessly.
- Add ai:rewriteText IPC and rewriteText() method for plain-text
  polishing via a separate callOllamaText() that skips JSON mode.
- Add "AI Rewrite" section in the editor right panel: textarea + AI
  button that rewrites whatever the user types in place.
- Improve buildAiPrompt() rules: action-focused title instructions,
  explicit anti-junk rules (no "Capture the screen / OCR" blocks),
  and a context-quality gate that suppresses blocks when context is
  thin.
- Add autoDoc checkbox to AI settings dialog.
- Renderer handles step:updated to reload the selected step after
  background auto-doc finishes.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-24 09:17:31 -05:00
Tyler 33eb0f745e semantic titles ai assist 2026-06-23 17:46:33 -05:00
Tyler 058582d7af Add Windows installer packaging
Template tests / tests (push) Successful in 1m40s
2026-06-23 11:01:43 -05:00
Tyler 3def598528 Tighten battery capture: cover lazily-spawned capture processes, widen warmup
Follow-up polish after the EcoQoS fix landed and recordings improved:

- Re-apply the EcoQoS opt-out when the stream backend reports it is active.
  Chromium spawns/upgrades its GPU and screen-capture utility processes only
  once the desktop stream actually starts — after the session-start sweep —
  so they could still be born throttled. Hooking the capture:state transition
  to 'stream' catches them the moment they appear.

- Raise the warmup cap from 1500ms to 3000ms. The recorder window stays
  visible during warmup (the user hasn't started their workflow yet) and the
  common path still proceeds the instant the stream is ready, so the extra
  headroom only matters when stream startup is slow on battery — where it
  buys a real pre-click frame for the very first click instead of a
  post-click fresh shot.
2026-06-16 08:15:46 -05:00
Tyler f580759337 Stop battery EcoQoS from starving frame capture during recording
Template tests / tests (push) Successful in 1m41s
Confirmed cause: on battery in a power-saving plan, Windows applies Power
Throttling (EcoQoS) to background work. StepForge records with its window
hidden, so the frame-capture worker renderer (plus the GPU and screen-capture
utility processes feeding it) get CPU-throttled. The throttled worker can't
sample the screen fast enough, so every click finds no fresh pre-click frame
and falls back to a slow post-click fresh shot — producing out-of-order,
dropped steps. It only reproduced on battery; plugged in (where EcoQoS stays
off even in eco mode) it worked. The capture log showed every click detected
but every one reporting "no frame qualified", with a ~4.6s stream startup.

Fixes:
- Chromium switches (disable-background-timer-throttling,
  disable-renderer-backgrounding, disable-backgrounding-occluded-windows) so
  Chromium stops de-prioritising and timer-throttling the hidden worker.
- New app/win-power.js opts the OS processes out of EcoQoS via
  SetProcessInformation(ProcessPowerThrottling, EXECUTION_SPEED off) and
  raises them to high priority. Applied to all live Electron processes when a
  session starts/resumes, and to the worker renderer the moment it is created
  (before it begins streaming). Best-effort, no-op off Windows.

The earlier mouse-hook EcoQoS opt-out already fixed click *detection*; this
fixes the frame-capture side that the same throttling was breaking.
2026-06-15 18:45:50 -05:00
Tyler 5edf740fda Fix capture degrading in eco/power-saving mode; decouple from display refresh rate
Template tests / tests (push) Successful in 1m47s
Two changes:
- Hold powerSaveBlocker.start('prevent-app-suspension') for the duration of
  each recording session. Windows Power Throttling (EcoQoS) can CPU-starve
  the capture-worker renderer mid-session, causing stream frame requests to
  time out and degrade to the slower in-process legacy loop. The blocker
  signals to the OS that this is an active workload and keeps the stream
  worker and main-process event loop running at full speed. Released on
  pause and finish.
- Remove maxFrameRate: 30 from the getUserMedia constraint in the capture
  worker. The actual sampling rate is driven entirely by the setInterval
  timer, so the stream's native frame rate is irrelevant to capture
  correctness. Capping at 30 could silently reduce stream delivery on some
  drivers when the display itself runs at ≤30 Hz (common in power-saving
  mode), leaving the ring buffer with fewer fresh frames at click time.
2026-06-15 18:20:57 -05:00
Tyler c39262c3fa Fix global placeholders save crashing with non-serializable error
Template tests / tests (push) Successful in 1m40s
The placeholders:globals:set IPC handler destructured { values } from
its args, but the renderer sends the placeholders object directly,
so values was always undefined and JSON.stringify(undefined) threw.
2026-06-15 18:08:16 -05:00
Tyler 8cc1ba3532 Add Wiki.js markdown export option 2026-06-15 13:59:04 -05:00
TylerandClaude Sonnet 4.6 0b490e2aa9 Add guide metadata, redesign PDF cover, paginate steps per-page, and run exports in a helper process
Lets users record author/co-authors/organization for a guide via a new
"Guide information…" dialog; this metadata renders below the title on
the PDF cover (title now sits above the accent rule). PDF export also
paginates so each step fits its own page where possible, keeps a
step's title/image/lead-in together, and forces the next step onto a
fresh page after an oversized step overflows. Exports now run in a
forked helper process so large guides no longer freeze the UI.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-06-15 12:54:31 -05:00
Tyler 58b4182c54 Add global undo for step deletion; simplify capture session UI
Template tests / tests (push) Successful in 1m41s
Template tests / tests (pull_request) Successful in 1m39s
- The canvas Undo/Redo buttons now also undo/redo step deletion
  (single or multi-select), restoring the step's data, images, and
  position in the order. Backed by a new step:restore IPC call and
  GuideStore.restoreStep().
- Remove "Finish capture session" from the capture menu. The
  top-right recording bar in the guide editor is now the only place
  to start/stop recording, with its toggle relabeled
  "Start recording" / "Stop recording".
2026-06-15 10:15:15 -05:00
Tyler c7038a151a Remove library auto-start and shoot button
Template tests / tests (push) Successful in 1m40s
2026-06-13 14:40:57 -05:00
Tyler 85b1f6f143 Use in-app modal for recording start confirmation
Template tests / tests (push) Successful in 2m17s
2026-06-13 13:50:13 -05:00
Iisyourdad e8f6e4cd09 Require recording acknowledgment before hide
Template tests / tests (push) Has been cancelled
2026-06-12 13:53:16 -05:00
Iisyourdad f88ff0259e Fix guide editor issues 4-10
Template tests / tests (pull_request) Has been cancelled
Template tests / tests (push) Has been cancelled
2026-06-12 11:07:57 -05:00
IisyourdadandClaude Fable 5 f2c5831315 Fix restarted recordings dropping clicks / stopping after one click
Template tests / tests (push) Successful in 1m51s
Template tests / tests (pull_request) Successful in 1m51s
Root cause: warm-before-hide kept the window visible during backend warmup,
and on a restart that warmup could take several seconds (the stream backend
start waits up to 8s). During that visible window, clicks over the app were
skipped by the userIsInApp guard and clicks elsewhere were shot post-click,
so a restarted session looked like it stopped after one click.

- Recording is now 'armed' only after the window is hidden and the buffer is
  primed. A new warmingUp flag makes onOsClick ignore clicks during warmup
  (the window is covering the user's work anyway) instead of mishandling
  them. Cleared on pause/finish.
- armRecording caps the warmup wait (WARMUP_MAX_MS=1500): the window hides
  and the session arms even if the backend start hangs, so it can never sit
  visible for seconds dropping clicks. The backend keeps coming up in the
  background; the first click or two may take the fresh-shot fallback.
- A generation token invalidates an in-flight backend start whose session
  has since finished, so a slow start can't install into a new session or
  leave the starting-guard stuck and block the restart from starting one.

Tests: 4 new behavioral capture tests (warmup ignores clicks; pause/finish
clear it; armRecording warms-then-hides-then-arms; a hung start still arms
within the cap; a stale start is discarded and frees the guard) plus a new
end-to-end self-test scenario (warmup click ignored, first armed click
captured). 152 unit tests + all repo checks pass.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-12 09:17:59 -05:00
IisyourdadandClaude Fable 5 3d0b753205 Add a 200ms click debounce with extensive behavioral tests
Template tests / tests (push) Successful in 2m1s
Template tests / tests (pull_request) Successful in 1m50s
Per request: clicks of the same button closer together than
capture.clickDebounceMs (default 200ms) now collapse into a single step, so
accidental fast/double clicks don't each become a step. It is a leading-edge
debounce measured from the last *accepted* click, so a run of fast clicks
can't push the next deliberate click out — two clicks spaced beyond the
window (e.g. the reported 400-500ms apart) always register.

Replaces the prior 8ms duplicate-delivery suppression (subsumed by the
window). Configurable; 0 captures every click.

Tests (the point of this change is that it can't silently regress):
- 13 behavioral unit tests in capture.test.js that drive real onOsClick
  calls with controlled timestamps and assert which clicks survive — the
  reported 400/450/500ms cases, sub-window collapse, the 200ms boundary,
  per-button independence, configurability, debounce=0, last-accepted (not
  last-dropped) reference, session reset, and a full onOsClick -> queue ->
  store integration check. No keyword/comment assertions.
- A fourth end-to-end self-test scenario (burst of 40ms clicks collapses to
  1; three 300ms-apart clicks each register => 4 total). The marker/drain
  scenarios set debounce to 0 so they keep stressing the frame pipeline.

147 unit tests + all repo checks pass.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-12 09:02:51 -05:00
IisyourdadandClaude Fable 5 0ab29e4ff0 Warm the frame recorder before hiding the window at recording start
Template tests / tests (push) Successful in 1m52s
Template tests / tests (pull_request) Successful in 1m46s
The first screenshot of a session was late while every later one was fine.
Cause: on 'Start recording' the window hid first and the capture backend
started warming up after — creating worker, getUserMedia, first frame takes
~1s. A click in that gap found no buffered frame and took the post-click
fresh shot.

armRecording() now warms the recorder while the window is still visible and
only hides once frames are buffering (with a brief post-hide settle so the
first frame shows the user's screen, not the dismissed app window). Verified
end to end with a new self-test scenario that clicks 250ms after start: the
first click is now served a pre-click frame instead of a post-click shot.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-12 08:48:54 -05:00
IisyourdadandClaude Fable 5 951bba7a21 Fix dropped clicks and late screenshots in fast recording
Template tests / tests (push) Successful in 2m13s
Template tests / tests (pull_request) Successful in 1m48s
Root cause of 'I clicked many times but only got two screenshots':
finishing/pausing a session called backend.stop(), which cancelled every
in-flight frame request to null. Clicks whose PNG had not finished
encoding yet were then dropped — only the first few survived.

Fixes:
- Stream backend now *drains* on stop: it stops accepting new requests but
  keeps the worker alive until frames already selected for queued clicks
  finish encoding. stop({ immediate: true }) keeps the old abandon-now
  behavior for an unhealthy worker.
- Two-stage worker reply: a fast 'frame-selected' ack pins the pairing and
  proves liveness; the slow PNG payload follows. A slow encode (seconds on
  software-rendered hosts) is no longer mistaken for a dead worker, which
  had been forcing the post-click fresh-shot fallback (late screenshots).
- Queued clicks carry their guide id and are stored even if the session
  ends while they wait in the queue.
- The tray gesture that stops a session is discarded by matching its
  recorded screen position, not a time window — a fast workflow click near
  the stop is no longer collateral damage. (Replaces the earlier grace
  window, which dropped whole bursts.)
- A click on a display with no ready stream resolves null so the caller
  fresh-shots the correct monitor instead of returning another screen.
- STEPFORGE_CAPTURE_LOG=1 prints one line per click decision; the
  second-instance handler now surfaces the running window instead of
  exiting silently.
- Self-test gains a fast-burst-then-finish scenario (8/8 saved) and the
  marker/coordinate checks remain at 0.00% offset.

Tests: 133 unit + all repo checks passing.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-12 07:56:31 -05:00
IisyourdadandClaude Fable 5 5ca59805dc Make click selftest immune to real mouse input and DPI scaling
Template tests / tests (pull_request) Successful in 1m48s
Template tests / tests (push) Successful in 1m51s
- Stop the live OS click watcher during the selftest so the user's real
  clicks (toast dismissal, terminal focus) can't add extra steps and
  shift the marker comparisons.
- Inject clicks in physical pixels via dipToScreenPoint so the test
  measures correctly on scaled Windows displays.
- Guard the marker report against step-count mismatches instead of
  crashing on out-of-range indexing.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-11 22:01:37 -05:00
IisyourdadandClaude Fable 5 a0b69f8cc7 Rearchitect click capture: strict click-time frames, off-main-process recorder, exact marker coordinates
Template tests / tests (push) Successful in 1m50s
Implements the architecture change from ai_prompts/prompt3.md:

- New app/click-frames.js: shared timestamped frame ring + strict
  click-to-frame pairing (never a frame whose grab started after the
  click); legacy slack behavior kept behind capture.strictClickFrames=false.
- New stream capture backend (app/stream-backend.js + hidden worker
  window): per-display desktop media streams sampled into ring buffers
  and PNG-encoded entirely off the main process, so click delivery is
  never starved by capture work. Auto-degrades to the legacy in-process
  frame loop when streams cannot start or the worker stops answering.
- Clicks are paired with their frame at event time (eager pairing in
  enqueueClickCapture); only the storing is serialized, so slow encodes
  cannot skew later clicks in a fast burst.
- Linux watcher: restored event-time root coordinates from
  xinput test-xi2 and merge raw/regular twin events structurally.
- Replaced the 40ms time debounce with source-aware duplicate
  suppression: fast legitimate clicks are never dropped.
- New app/coords.js: physical-to-DIP conversion with multi-monitor and
  scale-factor handling; Windows keeps screenToDipPoint.
- STEPFORGE_CLICK_SELFTEST end-to-end hook: 3/3 clicks become steps via
  the stream backend with 0.00% marker offset on this host.
- Tests rewritten/added: strict selection, coords, stream backend,
  Linux coordinate parsing, twin merge, burst clicking (126 passing).

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-11 21:33:31 -05:00
Iisyourdad 33c421b746 Added select button toggle for trash library grid
Template tests / tests (push) Successful in 2m0s
2026-06-11 11:31:58 -05:00
IisyourdadandClaude Fable 5 99376fdeb2 Fix new-capture auto-hide, library capture bar, step delete, and click-capture timing
Template tests / tests (push) Successful in 1m49s
- New Capture sessions now start paused; the window only tucks away once
  the user clicks "Start recording" in the capture bar instead of hiding
  ~1.2s after starting.
- The capture status bar is shown only in the editor view, not over the
  library.
- Fix openModal/confirmDialog resolving as cancelled when an action button
  is clicked, which made the step "Delete" button (and other modal actions)
  silently no-op.
- Click-triggered captures now use the click-time cursor position for the
  marker and arm the capture cache as soon as recording starts, so the
  first click is captured instantly and accurately placed.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-11 09:21:19 -05:00
IisyourdadandClaude Fable 5 25bca7c3de Session UX: tray-controlled recording, no per-shot window hiding
Template tests / tests (push) Failing after 24s
Users couldn't click into the app mid-session: every automatic capture
hid the window for the shot, so it vanished under the cursor. Under
WSLg minimize() is a no-op and isFocused() sticks true, so neither can
be used for control.

- Sessions now hide the window once at start and show a red tray icon
  with Capture now / Pause-Resume / Open StepForge (auto-pauses) /
  Finish; finishing or quitting restores/cleans up properly
- Opening the app from the tray pauses capture; resuming tucks the
  window away again
- Automatic captures skip while the cursor is over a visible StepForge
  window (cursor-based, not focus-based, due to WSLg sticky focus)
- Per-shot latency reduced: with the window already hidden the 350 ms
  hide-repaint wait is skipped entirely
- OS notification announces the session; self-tests updated and green

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-10 22:51:01 -05:00
IisyourdadandClaude Fable 5 52fd516a5d Make capture sessions continuous: click-capture + interval auto-capture
Template tests / tests (push) Failing after 29s
The session previously only listened for the global hotkey, which is
unreliable under WSLg/Wayland — users got one screenshot and nothing
more. Sessions now layer three triggers:

- click-capture via OS adapters (xinput test-xi2 on X11, PowerShell
  GetAsyncKeyState polling on Windows), debounced, ignoring clicks on
  StepForge itself
- interval auto-capture (3/5/10 s) as the always-works fallback,
  enabled by default when click detection is unavailable
- the existing global hotkey, plus a manual Shoot button

The REC bar now shows live count + active trigger with Shoot / Auto /
Pause / Finish. New captures and added steps are selected in the
editor (explicit reload(stepId) wins over a surviving selection).
Capture self-test hook (STEPFORGE_CAPTURE_SELFTEST) verifies 3x
hotkey-path captures and interval capture end-to-end.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-10 22:33:12 -05:00
IisyourdadandClaude Fable 5 6e790832f5 Complete the app: capture UI, dialogs, template manager, shortcuts help
Template tests / tests (push) Failing after 21s
- Editor topbar reworked: Back | Capture ▾ (full screen/window/region/
  delay/paste/import/session) | Save | Export | Share (.sfgz) | More ▾
  (rename, guide placeholders, backups, linked guide, shortcuts,
  settings)
- New dialogs: backups & snapshots (undoable restore), guide/global
  placeholder editor, keyboard-shortcuts reference, template manager
  (rename/duplicate/delete/share/import .sfglt)
- Export dialog: editable per-format options generated from exporter
  defaults, save-as-template, preview opens the file in the default
  viewer and keeps the dialog open for tweaking
- export:defaults IPC + preload entry
- CSS for blocks panel, focused-view sliders, export options, rows
- ipc-surface test: every preload channel has a main handler; renderer
  api.*/dialogs.* usage stays within the exposed surface (60 tests)
- CHANGELOG/README updated; prompt2.md checklist fully ticked

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-10 22:15:15 -05:00
IisyourdadandClaude Fable 5 03bd9b0e2b Fix renderer scope collisions, editor bugs; add welcome screen
Bug fixes from code review:
- Wrap renderer modules (canvas/dialogs/editor/app) in IIFEs: duplicate
  top-level 'const api' across plain scripts threw a SyntaxError that
  prevented app.js from ever running (blank window), and dialogs.js/
  editor.js silently overrode each other's labeledRow/makeSelect
- Focused-view toggle now writes step.focusedView.enabled instead of a
  nonexistent flat field that the schema dropped on save
- Annotation property edits no longer rebuild the panel on every
  keystroke (focus was stolen mid-typing); debounced save instead
- flushStep/undo/redo keep this.steps in sync with stepMap so the step
  list stops going stale after the first save
- Escape now deselects the annotation; Delete remains the delete key

Welcome screen (per spec): app opens to a title at top and three
buttons at the bottom — New Capture (creates a guide, opens the editor,
starts a capture session), Existing Workspace (library), Settings.
Brand click returns to the welcome screen.

Adds an env-gated dev screenshot hook (STEPFORGE_SCREENSHOT[_JS]) used
to visually verify welcome/library/editor views under WSLg.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-10 21:29:14 -05:00
Iisyourdad f47aca67c2 Finish Electron shell and workflow wiring
Template tests / tests (push) Failing after 4s
2026-06-10 18:32:30 -05:00