Status: Current V1 Studio contract
Owner surface: fixthis-mcp
| Surface | DOM id | Label |
|---|---|---|
| Prompt copy | copyPromptButton |
Copy Prompt |
| Agent handoff | sendAgentButton |
Save to MCP |
| Canvas select tool | selectToolButton |
Select |
| Canvas annotate tool | annotateToolButton |
Annotate |
| Pending target secondary action | inspectorFooter[data-editor-state="pendingTarget"] [data-action="cancel"] |
Cancel |
| Pending target primary action | inspectorFooter[data-editor-state="pendingTarget"] [data-action="addAnnotation"] |
Add annotation |
| Saved annotation destructive action | inspectorFooter[data-editor-state="saved"] [data-action="delete"] |
Delete annotation |
| Runtime diagnostics policy | runtimeEvidencePolicy |
Auto / Manual / Off |
| Saved annotation runtime evidence action | collectRuntimeEvidenceButton |
Capture diagnostics / Capture again |
| Refresh devices | refreshDevicesButton |
Refresh devices |
| Clear FixThis device selection | disconnectDeviceButton |
x icon |
| Workflow progress | workflowProgress |
FixThis feedback workflow |
| Prompt readiness | promptReadiness |
Prompt readiness |
| Compact history drawer | historyToggleButton |
History |
| Preview frame state | previewFrameStatus |
Live preview / Frozen for annotation / Saved screen / Stale frame / No screenshot / Interaction blocked |
- Select mode is the normal preview mode. Preview clicks navigate the debug app when the bridge is ready.
- Annotate mode freezes the latest preview so the user can select Compose nodes or draw visual areas.
- Stale preview state keeps the last preview visible while live bridge actions are disabled.
- The workflow progress surface uses
data-workflow-stepvaluesconnect,preview,annotate, andhandoff, withdata-statevaluescomplete,active, andupcoming. It must remain visible whenever the console shell is visible. - The prompt readiness surface stays visible near the handoff controls even
when
Copy PromptandSave to MCPare disabled. It must explain the empty, draft-only, ready-to-copy, and ready-to-save states without requiring a toast or failed click. - The preview frame state badge stays attached to the preview stage and must
use one of the canonical labels above.
Interaction blockedtakes precedence over stale or frozen labels;Stale frametakes precedence over a live frame when bridge actions are disabled but the last image remains visible. - Draft/history view shows persisted local feedback groups and sent handoff history.
- On compact layouts below the desktop history breakpoint, history remains
reachable from the top-bar
Historycontrol. Opening the drawer must expose the same saved evidence groups as the desktop history pane and support dismissal without changing the active session. - The global status surface (
#error.global-status) displays console-level error, warning, success, and info messages. Long bridge errors, partial handoff failures, and recovery diagnostics must wrap inside the viewport. - Saved annotation rows render
draft,sent,sent_modified,in_progress,needs_clarification,wont_fix, andresolvedas distinct lifecycle phases.needs_clarificationremains editable;wont_fixandresolvedare terminal agent outcomes. Agent notes and summaries appear in the saved annotation detail.
Annotatestarts targeting and freezes the latest available preview. It does not write a session item by itself.- Clicking a Compose node or dragging a visual area while a draft is frozen creates a browser-side pending annotation and focuses its detail editor immediately.
Add annotationis reserved for the transient pending-target state where a target exists but has not yet been committed; it must not appear while editing an existing draft annotation.Copy Promptpersists written pending annotations when needed, then copies compact agent-facing prompt text.Copy Promptmarks copied items withlastHandedOffAtEpochMillisso the browser can show prompt-handoff history, but it does not require or implydelivery: sent.Save to MCPpersists written pending annotations when needed, then creates a local handoff batch for MCP tools.- The session-level runtime diagnostics policy is persisted, not stored in
browser
localStorage. New sessions start at Auto; legacy sessions without the field decode as Manual. Policy updates are serialized per session and late results from another/replaced/closed session do not change the active control. - Auto makes
Save to MCPcollect the allowlistedbaselinepreset before the batch becomes sent. Manual skips automatic collection but keeps Capture diagnostics available. Off skips automatic collection and disables the console manual action. A typed terminal evidence result still lets valid feedback become sent and is surfaced in the success status and handoff. - Capture diagnostics appears on saved annotation detail and posts the
baselinepreset to/api/items/<itemId>/runtime-evidence/collectfor the owning session. The browser renders complete, partial, failed, unsupported, warning, summary, proximity, and artifact-path metadata; it does not stream or copy raw collector output into the console. Copy Promptnever starts automatic runtime collection. It renders only evidence already persisted for the item.Clear Draftdeletes unsent draft feedback after confirmation.- Live preview frames are transient. Persisted
screensare evidence snapshots, not every preview frame. - Browser-only pending work is stored as a schema-v2 DraftWorkspace envelope
under
localStorage["fixthis.workspace.<sessionId>.<workspaceId>"], with a per-session index atlocalStorage["fixthis.workspace.index.<sessionId>"]. The envelope carriesworkspaceId,revision,lifecycle, immutablecontext, frozenscreen,screenshotUrl,items, andhistory. Recoverable draft items are current-schema items and must carry client draft identity (workspaceIdplus per-itemdraftItemId) before they can be matched to persisted session items. - v0.4 supports schema-v2
fixthis.workspace.*draft recovery only. Pre-v0.4fixthis.pending.<sessionId>mirrors are ignored; usefixthis cleanor clear browser storage if an old local recovery entry is confusing the console. - On browser reload or session reattach, recovered pending work is not exposed automatically. The console shows an explicit Recover / Recapture / Discard banner; Recover is available only when the frozen preview context is present.
- Mutating saved/draft item APIs and preview/screen artifact URLs must carry the session that created the item, preview, or screen. They must not fall back to "current active session" when an explicit session id is available.
- Server-sent
session-updatedandpreview-readyevents carry top-levelsessionId. The browser applies them to detail/preview state only when that session is currently active, except for the initialsnapshotevent. - Live preview delivery is push-first.
preview-readySSE events are the normal automatic update path. Fallback preview polling runs only while/api/eventsis disconnected or unavailable, and both paths route through the same preview-application function and active-session fence.startLivePreviewPolling()returns immediately when the SSE connection is healthy (shouldUsePreviewFallbackPolling()is!consoleEventsConnected); under a healthy EventSource session no live-preview timer is created and zero steady-state/api/previewpolls occur (proven by the browser-reliability proof's zero-preview-poll assertion under healthy SSE, alongside the zero-session-poll assertion). The console performs exactly one bootstrap/api/previewfetch during page load — beforestartConsoleEvents()opens the EventSource — which is a one-time pre-connection load, not steady-state polling; the proof anchors its counters to the moment SSE reports connected to measure only the healthy window. sessions-updatedSSE events carry asummarypayload for the changed session when the server already has authoritative session state. The browser upserts that summary locally instead of fetching/api/sessionswhile EventSource is healthy.- Session polling (
/api/sessionsfetches on a 2-second timer) is a fallback-only path.startSessionsPolling()returns immediately when the SSE connection is healthy; under a healthy EventSource session, no polling timer is created and zero/api/sessionsfetches occur (proven by the browser-reliability proof's zero-session-poll assertion under healthy SSE). Polling resumes only on explicit reconnect, manual refresh, or SSE drop. - The
sessionsPollingPausedprojection is not healthy-path polling state; it is the retained disconnected-fallback reconnect signal. It surfaces the "Reconnecting feedback updates…" reconnect affordance only on the SSE-disconnected fallback path, and is intentionally kept to let that path recover. It is never set or rendered while EventSource is healthy. - SSE reliability boundary:
/api/eventsis the primary session, summary, device, connection, and preview sync path. Healthy EventSource sessions must not arm session or preview fallback polling and must not issue steady-state/api/session,/api/sessions, or/api/previewpull fetches after local mutations. The disconnected fallback remains live: EventSource errors recordeventsource_error, clear the connected flag, and re-armstartSessionsPolling()plusstartLivePreviewPolling(). Replay overflow is an explicit recovery path: the browser recordsreplay_overflowand performs a fullrefresh()from authoritative HTTP endpoints. - Browser-local SSE diagnostics are exposed only for local tests and debugging
through
window.FixThisConsoleDebug.consoleEventsDiagnostics(). They are not persisted in.fixthis/, MCP output, compact prompts, or feedback-session JSON. Both healthy-path no-poll guarantees (session and preview) are pinned by the zero-poll assertions inscripts/console-browser-reliability.mjs(assertNoSessionPollingUnderHealthySse), and the SSE-drop reconnect behaviour is exercised bytestEventSourceReconnectRecoveryin the same proof. - Interop boundary handoffs may include a
boundaryContextline derived from nearby Compose semantics. This context helps locate a likely Compose host, but it is not exact AndroidView/WebView source ownership. - Deleting a feedback session clears browser-local draft recovery for that session, including schema-v2 workspace entries.
Copy Prompt/Save to MCPcan persist the subset of pending annotations that have written comments.Copy Promptkeeps pin-only residual annotations browser-local so the user can resume them later.Save to MCPcompletes the handoff and discards residual pin-only annotations instead of carrying them into local recovery or session history.- Draft batch persistence is idempotent. The browser sends
workspaceIdand per-itemdraftItemIdvalues to/api/items/batch; the server stores those values asclientWorkspaceIdandclientDraftItemIdand uses the pair as the primary duplicate key. A full duplicate save must be a no-op and must not append a new event-log entry. A partial retry must append only the new draft items and reuse the already-persisted evidence screen when any incoming item matches by client draft key. - Current retry and browser recovery dedupe require client draft keys. Keyless browser-local recovery items are not semantically matched by target/comment; v0.4 drops them instead of promoting or double-counting unsupported pre-client-key local data.
- History row counts include persisted session items plus browser-local
draft/recovery items for that session. Completing
Save to MCPmust clear the residual local draft state for that action so the history count does not double-count saved items plus stale recovery items. - Async save, update, delete, undo/redo, and session refresh responses are fenced by the annotation context that issued them. If the user switches sessions while a mutation is in flight, stale responses are discarded or followed by a fresh session refresh before rendering.
- Persisted feedback item
sequenceNumbervalues are stable and monotonic within a session. Deleting or resolving a saved item does not renumber existing saved overlays or compact handoff item numbers. - Before persisting a pending batch, the console sends the frozen screen
fingerprint to
/api/items/batch. The server compares it with a lightweight current capture when both fingerprints exist. HTTP 409 witherror: "screen_fingerprint_mismatch"is recoverable UI state: prompt for re-capture, force-save, or cancel. - Browser request cancellation is normal local transport behavior. Console HTTP
routes must use
ConsoleHttpresponse helpers so client disconnects during response writes are closed quietly while unrelated server failures still surface as errors. - SSE
/api/eventscomputes its initial snapshot before committing streaming headers. Once the stream is open, keep-alive or event write failures caused by client disconnect close the subscription quietly.
Emitted by the server when console-build-meta.json mtime advances under the
--console-assets-dir watch path. Payload:
{ "buildHash": "<short git sha from console-build-meta.json>", "at": "<iso-8601>" }The packaged JAR never emits this event. The browser handler reloads only when
payload.buildHash !== window.FixThisConsoleConfig.buildHash.
devReloadEnabled: true— set only when the server is started with--console-assets-dir. Required for the browser to act onconsole-assets-changed.buildHash: <short git sha>— mirrored from the inlinedconsole-build-meta.jsonso the browser can dedup reload signals and the server build chip can display the current bundle SHA.
- The device-chip
xclears only FixThis's active device selection and owned bridge resources. - The device-chip
xmust not runadb disconnect, detach USB, or affect Wi-Fi ADB outside FixThis-owned resources.
Studio keeps the existing preview, connection card, annotation panel, and handoff controls. Exception handling is centralized as a workflow policy:
- Automatic recovery: heartbeat retry, preview refresh, session polling resume, app foreground recovery, and blocked-reason clearing may happen without a confirmation dialog. These actions do not mutate draft, handoff, claim, or resolve state.
- Confirmation-required mutations: dirty-draft session switch, stale preview Save to MCP, activity drift during handoff, server draft conflict, and stale force-save require a boundary dialog before durable state changes.
- Blocked mutations: unsupported builds, unavailable devices, missing active sessions, closed sessions, missing items, and in-flight durable mutations disable or no-op the requested action.
- Ignored stale responses: late SSE, late preview poll, late save, and generation-mismatched responses do not mutate a newer session or workspace.
Connection loss never clears a draft workspace, sent handoff batch, claim state, resolve state, or persisted evidence. Primary UI copy should describe the safe next action, such as "Connection paused - draft preserved"; raw bridge diagnostics remain in Details.
Save to MCPstores a local handoff batch.- Automatically collected runtime diagnostics are redacted and stored only
under
.fixthis/runtime-evidence/<session-id>/<capture-id>/. The console and MCP responses expose bounded summaries/status metadata, not raw collector bodies. Local artifact paths can still point to sensitive debug data and must not be committed. - Pending recovery envelopes remain browser-local until a handoff action
persists written items into
.fixthis/feedback-sessions/.Copy Promptmay leave residual pin-only recovery browser-local;Save to MCPclears residual pin-only draft state after the local handoff batch is created. - FixThis does not upload screenshots, comments, prompt text, source hints, or target evidence by default.
Rule: source hints are candidates; verify screenshot, target, and code before editing.
prompt = header rule package_line source_root_line? quality_line? "" screen_block+ footer runtime_evidence_attempt?
header = "# FixThis Feedback Handoff" ""
rule = "Rule: source hints are candidates; verify screenshot, target, and code before editing." ""
package_line = "- Package: `" pkg "`"
source_root_line = "- Source root: `" prefix "`" ; emitted iff ≥2 distinct candidate paths share a directory-boundary prefix ≥10 chars
quality_line = "Handoff quality: " quality_token (", " quality_token)*
screen_block = screen_header screenshot_line? viewport_line? activity_line? "" (overlap_block | item_block)+
screen_header = "Screen " short_id ": " display_name
short_id = first 8 chars of UUID
screenshot_line = "screenshot: " path
viewport_line = "viewport: " width "×" height ; emitted iff screenshot has dims
activity_line = "activity: " activity_name ; emitted iff != display_name
overlap_block = "Overlap group " N " (resolve one marker at a time):" item_block+
item_block = item_header id_line target_summary_line target_line crop_line? edit_surface_block source_block reliability_block ""
item_header = "[" N "] " title ; title may include severity prefix
id_line = " id: " item_id
target_summary_line = " target: " target_summary
target_line = " " [ "role=" role " " ] [ "tag=" tag " " ] "box=(" x1 "," y1 ")-(" x2 "," y2 ")"
[ " instance " i "/" total ]
[ "; targetRisk=overlap" ]
[ "; targetRisk=duplicate-of-marker-" M ]
crop_line = "crop: " path
edit_surface_block = (edit_surface_line edit_surface_action_line?){0,2}
edit_surface_line = " editSurface: " kind [ " role=" role ] " -> " file [ ":" line ] " conf=" lvl " why=[" terms "]" [ " basis=" text ]
edit_surface_action_line = " action: " text
role = "call-site" | "component-definition" | "copy-or-data" | "layout-or-style" | "visual-area" | "interop-risk"
source_block = candidate_line{1,3} caution_line?
candidate_line= " " file ":" line " conf=" lvl [ " owner=" composable ] " margin=" margin " matched=[" terms "]"
↑ owner/margin/matched are first line only; runner-ups omit them
; file is stripped of source_root prefix when present
caution_line = " note: " text ; emitted iff caution OR collision
reliability_block = target_confidence_line? target_action_line? warning_line* verification_line verify_before_edit_line runtime_evidence_block?
target_confidence_line = " targetConfidence=" ("high" | "medium" | "low" | "unknown")
target_action_line = " targetAction=" ("inspect-source-first" | "inspect-and-corroborate" | "treat-source-paths-as-hints" | "verify-manually")
warning_line = " warning: " text
verification_line = " verify: " ("source-first" | "corroborate" | "hint-only" | "manual") " because=" reason_token ("," reason_token)*
verify_before_edit_line = " verifyBeforeEdit: " action_token ("," action_token)*
runtime_evidence_block = " runtimeEvidence:" runtime_evidence_line{1,3}
runtime_evidence_line = " - " type " -> " artifact_or_no_artifact "\n summary: " bounded_text
footer = "---"
"agent_protocol:"
" before_work: fixthis_claim_feedback({sessionId, itemId})"
" on_complete: fixthis_resolve_feedback({sessionId, itemId, status: resolved|wont_fix|needs_clarification, summary})"
" user_console_reflects_within: 2s"
"session_id: " session_id
runtime_evidence_attempt = "" "runtimeEvidenceAttempt:"
" attempted=" boolean
" status=" ("complete" | "partial" | "failed" | "unsupported" | "skipped")
(" failure=" failure_reason)?
(" reason=" ("manual" | "off" | "skipped"))?
(" warning=" warning_token){0,8}
The screenshot: line is optional and omitted when no screenshot artifact is available for the screen.
The crop: line is optional and emitted only when a per-item screenshot crop path is available.
When no source candidates are available for the item, the source block consists of a single unknown line.
N— 1-based annotation number matching the numbered overlay marker.short_id— first 8 characters of the screen UUID.viewport:— screen dimensions in pixels; emitted only when the screenshot artifact has width and height metadata.activity:— Android activity name; emitted only when it differs fromdisplay_name.Source root:— directory-boundary common prefix of all candidate file paths in the session, with trailing/. Emitted only when at least two distinct candidate paths share a prefix of ≥10 chars; otherwise omitted and candidate paths are written verbatim. When emitted, the prefix is removed from every candidate line so each candidate appears as a relative path.Handoff quality:— optional aggregate warning summary for the rendered item set. It is emitted only when at least one low-confidence target, warning, overlap group, duplicate marker, visual area, redacted target, stale source candidate, or item without source candidates exists.target:— redaction-safe semantic target summary derived from the selected node when available. It may includetag="...",text="...",contentDescription="...", androle=...in that order. For visual-area selections it renderstarget: visual area; for sensitive/editable/password targets it omits text-like values and rendersredacted sensitive target.- target line — emits only the tokens that have content.
role=andtag=are dropped when the correspondingselectedNodefield is blank; the line collapses to barebox=for area selections. [!]severity prefix — prepended to the title when item severity isHIGH; absent forMEDandLOW.id:— feedback item id. Agents should use this withfixthis_claim_feedbackbefore editing andfixthis_resolve_feedbackafter finishing.agent_protocol:— footer that repeats the queue contract inline for agents that only see copied Markdown.session_id:— feedback session id to pass with item ids when the agent needs to avoid relying on the active session default.instance i/N— emitted on the target line when multiple items on the same screen share the same(top_candidate file:line, testTag); index assigned bypath-leaf string sort order.targetRisk=overlap— present when the target participates in an overlap group (see below).targetRisk=duplicate-of-marker-M— present when this item is an exact duplicate of an earlier item (same source, testTag, path leaves, and bounds).editSurface:— optional inspection hint for visual/style/layout feedback. It is derived from the user comment, selected target, target owner, and source candidates. It does not replace source candidates and is not an auto-edit instruction.editSurface role=— optional role for the edit-surface hint:call-site,component-definition,copy-or-data,layout-or-style,visual-area, orinterop-risk.action:after aneditSurface:line is role-specific agent guidance. It is rendered from the optional edit-surface candidatenotefield and does not add a persisted JSON field.sourceCandidatesidentify where selected or nearby strings came from.editSurfaceidentifies where a style/layout change is likely rendered.- candidate lines — up to 3 in score order. Rank 1 includes optional
owner=<Composable>when the source index knows the enclosing@Composable fun,margin=(score gap to rank 2, formatted to 2 decimal places), andmatched=[...](up to 4 reason tokens). Runner-up lines include onlyconf=. note:— emitted when the rank-1 candidate has acautionfield, or when multiple items in an instance group share the same call site (collision note on the first item only).- A
SHARED_COMPONENTrisk flag renders its caution string through this samenote:/cautionpath and requires no console-side schema change, because the console already renderscautiontext and tolerates additionalriskFlagsmembers. - The Evidence section renders a "Shared component used at" row listing the top candidate's
callSiteswhen present. Absence ofcallSitesis tolerated (single-use definitions and older payloads omit it). - Confidence is lowercase:
high,medium,low, ornone. targetConfidence=— optional target-level reliability. It describes how much the selected UI target can be trusted before editing; it is not task priority and is distinct from source-candidateconf=.targetAction=— optional target-level action guidance. It is emitted as a separate line sotargetConfidence=remains a parseable enum-only confidence token (high,medium,low, orunknown).warning:— optional target-level caveats, such as visual-area-only, possible AndroidView/WebView interop, stale source index, forced screen mismatch, missing fingerprint, or sensitive text redaction.verify:— item-level agent verification posture derived at render time from target reliability, source-candidate confidence/margin/staleness, overlap/duplicate risk, target kind, and edit-surface hints.source-firstmeans strong target plus strong source evidence;corroboratemeans useful evidence that still needs screenshot, semantics, and code cross-checking;hint-onlymeans source paths are search hints;manualmeans evidence is missing or risky enough that the agent must verify manually before editing.verifyBeforeEdit:— comma-separated action tokens for the agent before changing app code. Current tokens areclaim-feedback,inspect-source,compare-screenshot,check-target-summary,review-edit-surface, andverify-manually. Future runtime-evidence tokens such ascheck-logcat,check-frame-summary, andcheck-memory-summaryare reserved for optional evidence attachments.runtimeEvidence:— optional local runtime evidence summaries attached to the item. Compact handoff renders at most 3 attachments per item, local artifact paths orno-artifact, and bounded summaries only. Raw logcat, frame, memory, or trace payloads are not emitted in compact Markdown.runtimeEvidenceAttempt:- appended to a persisted Save-to-MCP prompt after the agent-protocol footer. It records the final automatic/skipped decision using booleans, enums, mapped reasons, and at most eight warnings. It omits capture/attachment ids, artifact paths, commands, and raw output. Copy Prompt does not append this block because it never starts collection.npm run runtime-evidence:smokewrites a local runtime evidence report and defers missing Android prerequisites in non-strict mode. The strict variant (npm run runtime-evidence:smoke -- --strict) drives the real MCP tool, Auto Save-to-MCP handoff, bounded/redacted artifacts, restart replay, and item linkage. It fails when connected prerequisites or any product-path assertion are unavailable; generic direct logcat output cannot satisfy it.- Items with stale source candidates, visual-area targets, forced fingerprint
mismatch, sensitive redaction, interop warnings, overlap risk, or duplicate
marker references must not render
verify: source-first.
| Reason string | Token |
|---|---|
selected text |
text |
selected contentDescription |
contentDescription |
selected testTag |
tag |
selected testTag convention composable |
compTag |
selected role |
role |
selected resolved stringResource |
resolvedStringRes |
nearby text |
nearbyText |
nearby contentDescription |
nearbyContentDescription |
nearby testTag |
nearbyTag |
nearby role |
nearbyRole |
activity |
activity |
selected stringResource |
stringRes |
arbitrary literal |
literal |
legacy fallback |
legacy |
These tokens appear in matched=[...] on rank-1 candidate lines.
Precise and full Markdown handoffs render owner-aware source candidates as
<file>:<line> inside fun <Composable> when ownerComposable is present.
When two or more annotations on the same screen have targets that overlap (visual-area intersection IoSA >= 0.25, or weak-label center distance <= 24dp at default density 1.0), they are collected into an explicit overlap group:
Overlap group N (resolve one marker at a time):
Each item in the group carries targetRisk=overlap on its coordinate target line. Resolve the group one marker at a time to avoid editing the wrong composable. Coordinate space is window pixels at default density 1.0; the 24dp center-distance fallback is conservative on high-density screens.
Tool-using agents that parsed the v1 compact prompt format must update the following token patterns. The v2 format is strictly more verbose and uses different but equally parseable tokens; no information is lost.
| v1 token | v2 token | Rationale |
|---|---|---|
src? <file>:<line> <conf> (single line) |
indented <file>:<line> conf=<lvl> lines (up to 3, no header, no ~ prefix) |
Two-space indent + conf= token is enough to distinguish candidate lines for an agent. The header and ~ prefix were boilerplate. Runner-up candidates are now visible. |
bounds=L,T - R,B |
box=(L,T)-(R,B) |
Matches typical (x,y) notation. The earlier [W×H] size suffix was dropped — derivable from the box, and agents can compute it on demand. |
target: Node "tag" |
[role=<role> ][tag=<tag> ]box=... (with role=/tag= dropped when blank) |
Drops unvarying Node/Area fallback role and the tag=(none) placeholder. Each token is keyed and only emitted when it carries information. |
why=<token>+<token> |
matched=[<token>, <token>] |
Renamed for clarity: matched describes what evidence was found; why was ambiguous metadata. The same reason-token vocabulary is reused. |
risk=<token> (on source line) |
targetRisk=<token> (on the target coordinate line) and note: <text> (after candidate lines) |
Splits target-level risk (overlap, duplicate-of-marker-N) from source-level caution; v1 conflated them on one line. |
Screen UUID <full-uuid> |
<first-8-chars> (short-id) |
Reduces visual noise; 8 hex chars are unique enough for disambiguation within a session. |
| (absent) | viewport: W×H |
New: lets agents interpret pixel bounds without opening the screenshot. |
| (absent) | activity: <name> |
New: emitted when Android activity name differs from displayName. |
| (absent) | Handoff quality: ... |
New: summarizes aggregate target/source warning signals for the rendered item set. |
| (absent) | target: ... |
New: gives a redaction-safe semantic summary of the selected UI target before source candidates. |
| (absent) | instance i/N on the target coordinate line |
New: disambiguates list-rendered widgets that share a call site. |
| (absent) | note: N markers map to same call site — likely list-rendered; disambiguate by instance index |
New: collision signal on the first item of each instance group. |
| (absent) | targetRisk=duplicate-of-marker-M |
New: surfaces true marker duplication so agents do not double-resolve. |
| (absent) | - Source root: \`` header + relative candidate paths |
New (2026-05-10 trim): hoist the directory-boundary common prefix of all candidate paths once, strip from each candidate line. Net token saving on long monorepo paths; absent for sessions whose candidates do not share a prefix. |
The browser console uses one canonical ConsoleAppState. DOM events and
network responses dispatch commands/events into a reducer. The reducer returns
the next state plus effect descriptions; browser adapters execute those effects
and dispatch fenced results back into the store.
Renderers consume selector view models only. They do not mutate session,
preview, draft, tool, polling, or prompt state. Draft work is represented by
workspace.kind = "draft" and is locked to an immutable session/preview/screen
context until saved, moved to recovery, or discarded.
Browser-internal legacy state holders such as activeDraftFlow,
draftFeedbackItems, focusedPendingItemIndex, and currentSelection are not
supported. This does not change MCP tool contracts, HTTP route payloads,
persisted feedback-session JSON, or local draft storage migration
compatibility.
Cross-session navigation with unsaved draft work creates pendingBoundary; the
boundary sheet is the only UI path that can save, recover, discard, or cancel
that transition. The same boundary state renders as a centered modal on desktop
and a bottom sheet/full-width modal on mobile.
The console bundle is generated by scripts/build-console-assets.mjs from
the source files in fixthis-mcp/src/main/console/. Source order is a
topological sort over // @requires directives at the top of each file.
The build emits a minified app.js (≤ 240,500 B raw / ≤ 61,000 B gzip), an
external source map linked via //# sourceMappingURL=app.js.map, and a
console-build-meta.json sidecar that FeedbackConsoleAssets.kt inlines
into window.FixThisConsoleConfig.buildMeta at serve time.
Identifier minification is disabled (minifyIdentifiers: false) so
asset-contract tests grep for stable function names. A CONTRACT_SYMBOLS
list in the build script guards each test-asserted symbol against
accidental minifier elimination. Tests now read unbundled JS from
fixthis-mcp/src/main/console/*.js via ConsoleSourceFixtures (see
fixthis-mcp/src/test/kotlin/io/github/beyondwin/fixthis/mcp/fixtures/ConsoleSourceFixtures.kt)
rather than the minified app.js.
CAPTURE_UNAVAILABLE— emitted by the server when/api/previewor/api/screenshotreturns 404 or the capture has only semantics with no image bytes. The response stays HTTP 200 withpayload.previewAvailable = falseand areadinessobject built byFirstRunReadiness.captureUnavailable(cause, details)(primaryAction: "Retry capture"). Rendered by the console via the existing connection-card readiness slot (#connectionReadiness, populated byapplyPreviewReadinessToConnectionCardinpreview.js).
| dedupeKey | surface | severity | Fired by |
|---|---|---|---|
reload_console_403 |
banner | error | surfaceReloadConsoleNotice(err) in state.js, wired into requestJson catch in api.js. Triggered when requestJson throws a ConsoleRequestError with action === 'reload_console' (HTTP 403 from origin/token check). primaryAction "Reload console" calls window.location.reload(). |
clipboard_fallback |
banner | warning | copyPrompt in prompt.js when copyTextToClipboard rejects. Detail tells the user to copy manually. Suppresses the legacy showError toast and short-circuits the handoff/copied = true flow. |
The save endpoint now implements optimistic-etag concurrency:
- Successful save →
200 OKplus headerETag: "<rev>"(monotonic per session, in-memory inDraftSaveService.kt). - Subsequent save → must include
If-Match: "<previous-rev>". Missing or mismatched header →412 Precondition Failedwith body{ "state": "STALE_PREVIEW", "readiness": <FirstRunReadiness>, "serverDraft": <current draft snapshot> }. - Override sentinel —
If-Match: *forces the save regardless of the server revision (used by the "Keep mine (overwrite)" boundary branch).
Console handles 412 by opening the new staleDraftConflict boundary
variant with three buttons:
- Primary "Keep mine (overwrite)" → resends the save with
If-Match: *. - Secondary "Use server's version" → discards local pending state and
loads
serverDraftinto the workspace. - Cancel → keeps the local draft (pending pins preserved).
No automatic three-way merge.
sse.js (dropStaleSse(msg, state)) and
previewPoll.js (dropStalePreviewPoll(response, state)) drop messages
and poll responses whose sessionId does not match
state.session?.sessionId. The drop emits a console.warn for
diagnostics only — no state mutation, no NotificationCenter
notification, no UI change. Tested by scripts/sessionMismatchIgnore-test.mjs.
The console bundle budget is intentionally enforced by
node scripts/build-console-assets.mjs --check, but it carries working
headroom so small console changes do not fail on byte-level churn:
- Gzip budget -
61,000 B. - Raw budget -
240,500 B.
The state.js hotspot budget in ArchitectureHotspotBudgetTest was
bumped from 440 → 470 lines to accept the
surfaceReloadConsoleNotice helper.
FeedbackSessionException messages carry a machine-readable prefix followed by
a human-readable suffix, e.g. SESSION_CLOSED: Cannot run claim on a closed feedback session. Only the prefix (up to and including the colon) is the
stable contract. The suffix text is informational and may change between
versions — clients must not string-match on it. The console maps each prefix to
an HTTP status + errorCode in feedbackSessionHttpMappings
(FeedbackConsoleServer.kt).
| Prefix | Meaning | Notes |
|---|---|---|
SESSION_CLOSED: |
Mutation rejected because the session is closed | Single-sourced via SESSION_CLOSED_PREFIX (FeedbackSessionStoreDelegate.kt); produced by one helper so wording cannot drift by entry point. |
ITEM_ALREADY_RESOLVED: |
Claiming an item already in RESOLVED/WONT_FIX |
Claim guard; resolved items are terminal for agent claim. |
SESSION_NOT_FOUND: |
No session exists for the given id | |
NO_ACTIVE_SESSION: |
No current session is open | |
SCREEN_NOT_FOUND: / PREVIEW_NOT_FOUND: / PREVIEW_SCREENSHOT_NOT_FOUND: |
Requested screen/preview artifact is absent | |
NO_DRAFT_FEEDBACK: |
No draft items to act on | |
ITEM_NOT_EDITABLE: |
Item is not in an editable state | |
DEVICE_NOT_AVAILABLE: / PREVIEW_SAVE_IN_PROGRESS: |
Transient device/preview-save contention |