Analyze Workflow¶
Input¶
- Paste plan output: plain EXPLAIN JSON or typical
psqlQUERY PLAN cell text (header, separators,(N rows),+wraps—see Capturing EXPLAIN JSON). PreferEXPLAIN (ANALYZE, BUFFERS, VERBOSE, FORMAT JSON)when you need forensic evidence; plannerCOSTSare optional. - Optionally paste the source SQL query text (saved with the analysis when provided).
- Optionally expand Suggested EXPLAIN command to generate copy-paste SQL (toggles for
ANALYZE,VERBOSE,BUFFERS, and explicitCOSTS true/false) and, if Send EXPLAIN options with analyze request is checked, attach declared options plus an optional “recorded command” to the API payload (explainMetadata). - Click Analyze.
In-product workflow guide (Phase 101)¶
- Distinct help chrome:
help/helpSurface.css(dotted border, teal accent rail, “Guide — not plan analysis” kicker) is intentionally not the same surface family aspqat-panelfindings/workspace cards. - Entry points:
How to use Analyze/Hide guidetoggles theAnalyzeWorkflowGuidepanel; it opens automatically on an empty page and after Clear, stays closed while a snapshot or share link is open, and can be reopened anytime. - Context hints:
pqat-help-inlinecallouts sit beside Input plan and Graph/Text mode (capture vs interpretation—read the guide for definitions, not the forensic panels).
Workflow guide persistence & re-entry (Phase 102)¶
- Dismissal: Hiding the guide on an empty Analyze page sets
analyzeDismissedinlocalStoragekeypqat_workflow_guide_v1({ v: 1, analyzeDismissed?: boolean, compareDismissed?: boolean }). The next empty visit starts with the guide closed until the user clicks How to use Analyze, presses?or Shift+/ (when focus is not ininput/textarea/select/contenteditable), or opens/?guide=1or/?guide=true. - Clear: Clear always reopens the guide for a fresh capture pass (without clearing the stored dismissal flag for future empty visits).
- Deep link:
?guide=is removed from the URL when the user closes the guide after it was opened via that param, so refresh does not keep forcing it open. - Plan guide rail:
pqat-help-inlineunder Plan guide reminds users the rail is interpretation layered on the plan, not raw EXPLAIN output.
Help lifecycle, focus, and support entry (Phase 103)¶
- Focus: When the guide opens from How to use…,
?, or?guide=1, focus moves to the guideh2(programmatic focus,tabIndex={0}). Auto-open on first empty visit and Clear do not steal focus from the plan field. - Close: Esc closes the guide when the event target is not an
input/textarea/select/contenteditable(same ignore rule as?). After an explicit close (Hide guide or Esc), focus returns to the bar toggle. - Support: Guide footer Copy merged guided link / Copy entry guided link—merged uses
buildCopyGuidedLinkUrlFromLocation(keeps query + hash); entry usesbuildWorkflowGuideAbsoluteUrl(pathname)(?guide=1only). Both are distinct from Copy share link on a saved analysis. - E2E: Playwright proves
localStoragedismissal survives reload and that?guide=1still reopens; merged/entry guided clipboard + guide announcer lifecycle + Esc + focus return are covered inpersisted-flows.spec.ts.
Help accessibility and guided-link merge (Phase 104)¶
- Screen readers:
data-testid="analyze-workflow-guide-announcer"(aria-live="polite",aria-atomic="true") announces explicit open (“Analyze workflow guide opened”), close (“…closed”), and “Guided help opened from link” when?guide=opens the panel. Auto-open on first empty visit does not announce. Copy is cleared after a few seconds to limit noise. - Tab loop: After an explicit open (How to use…,
?,?guide=), Tab / Shift+Tab wrap between the first and last focusable insidepqat-help-shell(capture listener on the section). Clear / empty-state auto-open does not enable the loop. Esc and non-modal behavior unchanged. - Merged guided URL:
buildCopyGuidedLinkUrlFromLocationsetsguide=1on the livewindow.location(path + search + hash), preserving other params—distinct from Copy share link on a saved analysis.
Help landmarks, dual guided links, and browser announcer proof (Phase 105)¶
- Landmarks:
WorkflowGuideShellisrole="region"witharia-labelledbyon the guide title; the footer wrapper isrole="group"aria-label="Guided link sharing". - Guided links: Footer Copy merged guided link + Copy entry guided link (see Support above). Playwright asserts clipboard shape for each; Vitest covers
?guide=announcer viaMemoryRouter. - E2E announcer:
persisted-flows.spec.tsassertsanalyze-workflow-guide-announcer/compare-workflow-guide-announcertext for explicit open/close and “Guided help opened from link” after dismiss +?guide=1navigation.
Example entry, triage-first summary, lighter copy (Phase 107)¶
- Try a sample:
TryAnalyzeExampleChips(capture when no result yet + Analyze guide) loads bundled JSON fromsrc/examples/plans/(kept in sync withe2e/fixtures).onAnalyze(overridePlanText?)avoids stale state when running immediately after load. - Triage:
analyzeTakeawayFromResultdrivesanalyze-summary-takeaway(Start here): top finding → else first inspect step → else plan overview. Plan briefing keeps primary lanes; flow / index shape and long “why sections differ” text move behind<details>; Plan source & EXPLAIN metadata is collapsed by default. - Copy: Fewer “server / artifact” phrases on the primary path; persistence lines describe saved in this app’s database and share links in user terms.
- Tests: Vitest (example flow, takeaway helper); Playwright
persisted-flowsTry example smoke;data-testid*-capture/*-helpsuffixes avoid duplicate id collisions.
Scan-first summary, expanded samples, findings cascade (Phase 108)¶
- Examples: Three Analyze samples at ship (simple seq scan, sort pressure, index + ordering) plus two Compare pairs — see
src/examples/README.md. Phase 109 adds a fourth Analyze sample (join-heavy nested loop). - Triage strip:
analyzeFollowUpScanSignalsrendersanalyze-scan-signals(Also scan) with jump buttons; headinganalyze-summary-heading(Summary); Plan metrics live under Analysis id & plan counts<details>; Plan briefing shows Start here inspect lane first, with overview / work / drivers collapsed. - Cascade:
primaryTriageFindingIdfrom the takeaway highlights the matching Findings row (Matches summary cue). - E2E:
analyze-optimization-suggestions-panelstabilizes Copy for ticket; Analyze flow waits onanalyze-summary-headinginstead of the old longer title string.
Virtualized findings + suggestions tied to triage (Phase 110)¶
- Findings: Long lists still scroll the Matches summary row into view via
VirtualizedListColumnscrollToIndex(same primary finding as Start here). - Suggestions: Evidence alignment with Start here (Phase 111 expands this—see below).
- Motion: Programmatic scroll respects
prefers-reduced-motion(motionPreferences);scrollArtifactIntoViewuses the same helper. - Narrow: Sticky summary/triage uses
--pqat-sticky-top-offsetso bands sit below the app top bar.
Guided-path cohesion + triage suggestion expansion (Phase 111)¶
- Suggestions:
suggestionAlignsWithAnalyzeTriagemarks cards Aligned with Start here whenrelatedFindingIdsmatches,triageFocusNodeIdis intargetNodeIds, or suggestion targets overlap the primary finding’snodeIds. - Plan guide:
analyze-guide-jump-primary-findingscrolls the Findings list to the Start here row when the takeaway is finding-driven. - Selected node: When the node-scoped suggestion is the top-ranked experiment and aligns with triage, a compact cue (
analyze-selected-node-top-next-cue) ties Next steps to Start here. - Sticky:
useStickyTopOffsetSync(inApp) measures.topBarand sets--pqat-sticky-top-offseton resize so narrow sticky bands track real chrome height.
Triage continuity, exports, calmer system surfaces (Phase 112)¶
- Virtualized suggestions: When the list is virtualized and a row aligns with Start here,
AnalyzeOptimizationSuggestionsPanelscrolls that row into view viaVirtualizedListColumnscrollToIndex(firstVirtualRowIndexAlignedWithAnalyzeTriageinanalyzeOutputGuidance.ts). - Plan guide:
triagePrimaryRoute— finding-driven takeaways keep Open Start here finding in list; step-driven takeaways get Jump to plan focus (analyze-guide-jump-triage-plan) instead of a no-op finding jump. - Exports / share parity: Client-side
analyzeExportTriage.tsaugments downloaded Markdown (preamble), HTML (injected block), and JSON (pqatExportTriageenvelope) with compact Start here + primary finding id when known. Copy share link / deep-link clipboard payloads can include a Start here headline line (shareAppUrl.analyzeDeepLinkClipboardPayload). - System copy:
artifactErrorPresentationtightens error titles; capture panels use calmer reopen/loading lines; summary persistence hint is slightly less mechanical. - Sticky: Narrow sticky shells add
env(safe-area-inset-top)on top of--pqat-sticky-top-offset(workstation.css) for notched / inset layouts.
Guided-flow cohesion, error next steps, virtual proof (Phase 113)¶
- Plan guide echo:
analyze-guide-triage-echono longer repeats the Start here headline—one line points back to the summary band; jump buttons unchanged. - Selected node: Continuity strip references the summary headline without duplicating it; top-next cue shortened.
- Errors:
artifactErrorBannerNextStepadds a single actionable line underArtifactErrorBannerwhen the pattern is known (not found, corrupt, schema, plan parse, network). - Virtual scroller test id:
analyze-suggestions-virtual-scrolleron long suggestion lists; Playwrightpersisted-flowspads/api/analyzeresponses to force virtualization, targets the single scroll-target rowanalyze-suggestion-card-triage-aligned(first triage-aligned virtual row), and asserts viewport visibility plus intersection with the scroll container (Phase 114), not only DOM presence of Aligned with Start here. - Plan guide echo (Phase 114): When the summary path is overview + Also scan (not a finding/step Start here thread), the rail uses wording that matches that path.
Export trust, reopen parity, clearer failures (Phase 116)¶
- Reopen → export: With
analysis=in the URL and an empty plan box, exports callPOST /api/report/markdown(and siblings) with{ analysis: <PlanAnalysisResult> }—the same snapshot the UI already loaded. The capture panel showsanalyze-export-snapshot-cueand per-format Preparing… busy text; success/error status lines are separate from global analyze errors. PlaywrightAnalyze: reopen with empty plan input exports markdown using snapshot payloadasserts the POST JSON and a substantive markdown body (same pattern as Compare reopen→export). - Guide echo:
filterTriageEchoScanLabelsremoves Also scan lines that only repeat the Start here headline (case/space normalized). - API errors: The SPA
formatApiErrorResponsehelper maps structured4xxJSON (including empty bodies) to a single actionable sentence for export failures. - Backend:
ArtifactPersistenceJson.ApplyToHttpSerializerOptionsapplies persistenceJsonSerializerOptionstoConfigureHttpJsonOptionsso snapshot/report deserialization cannot drift from SQLite artifact JSON (converters are not registered twice if applied repeatedly).
Clearer export failures, Compare parity, quieter guidance (Phase 117)¶
- Report API errors: Malformed or unusable bodies on
/api/report/*return compact JSON (request_body_invalidorexport_request_incomplete) instead of an empty 400;formatApiErrorResponseshows themessageline without prefixing technicalerrorcodes for those. - Export status:
analyze-export-statusannounces success/failure witharia-live="polite"andaria-atomic="true"(same pattern as Compare). - Guide echo: When the summary path is overview and Also scan chips are present, the rail echo drops the extra headline paragraph so the list is not framed twice.
Stable export validation, visible failure proof, trust copy (Phase 118)¶
- Semantics:
export_request_incompletefor empty{}analyze report bodies is decided inReportExportValidation(explicitResults.BadRequest). Malformed JSON still maps torequest_body_invalidviaIExceptionHandler. - Playwright:
Compare: reopen with empty plan inputs exports HTML using snapshot payload;Analyze: export failure shows calm status line without error-code prefix(mocked 400 on/api/report/markdown, assertanalyze-export-status). - Sharing panel: Calmer ArtifactSharingPanel titles and local-dev hints (
artifact-sharing-detailsunchanged).
Take-with-you export, format parity, quieter echo (Phase 119)¶
- Capture: Eyebrow Take with you (replaces Download); shorter snapshot vs paste hints; button
titles name format role (Markdown / HTML / JSON). - Success: Export ready / Export started status copy after export (outcome-focused).
- Parity: Playwright
Analyze: reopen with empty plan input exports HTML using snapshot payload(POST /api/report/html). - Guide echo: Summary rail headline hidden whenever Also scan chips exist (finding/step/overview), not only overview.
- Failures:
formatApiErrorResponsemaps ProblemDetails-style 500 JSON (title/ safedetail) without echoing stack traces indetail.
Final export parity, handoff clarity, quieter triage echo (Phase 120)¶
- Parity: Playwright
Analyze: reopen with empty plan input exports JSON using snapshot payload(POST /api/report/json) — request body includesanalysissnapshot; responseanalysisIdmatchesPOSTbody and includesnodes. - Take with you: Compact per-format legend (
analyze-export-format-legend) under the snapshot/paste hint; success status uses Ready / Started (short, download-focused). - Sharing:
artifact-sharing-effect-note— saving changes who can use the link next, not offline copies; post-save line Sharing saved. New access rules apply the next time someone opens this link. - Guidance:
filterTriageEchoScanLabelsalso drops Also scan chips that duplicate the headline’s opening phrase (not only exact matches);compareContinuityCueIsSpecifictreats short outcome cues (worsened / improved / Plan A/B) as specific enough for the pair panel. - Trust: Empty-body 400 on export —
formatApiErrorResponsesuggests reload and re-pasting the plan before retrying. - Visual follow-up:
analyze-export-report-row/analyze-export-format-legendare stable targets for a futuree2e-visualcrop (run Linux--update-snapshotswhen ready).
Action handoff polish, unified export voice, continuity tests (Phase 121)¶
- Take with you:
pqat-handoffBandtop border separates the export block;analyze-export-handoff-kickerstates the decision;pqat-formatLegenduses Markdown / HTML / JSON labels with roles (notes, print, tools); snapshot hint distinguishes saved-link vs plan-box rebuild. - Export success:
exportDownloadSuccessHint(snapshot|fromPlanText) shared by Analyze and Compare status lines. - Failures:
formatApiErrorResponse— empty 401 and calmer empty 413;throwFormattedHttpErrorand other 401 paths use the same formatter (no duplicate dev-token string for report posts). - Sharing:
artifact-sharing-effect-note+ post-save line tightened; auth card title How access is checked. - Guidance:
compareContinuityCueIsSpecificadds a short faster/slower + plan-vocabulary path; moreresolveComparePairFallbackDisplaytests;filterTriageEchoScanLabelsregression test for non-echo chips.
Readability-first flow, graph de-emphasis, graph→finding continuity (Phase 122)¶
- Graph footprint: Default graph height
clamp(240px, 30vh, 420px)(Phase 137 retune; wasclamp(260px, 34vh, 460px)in 122); paired workspace grid gives the plan guide column slightly more width on medium/wide breakpoints; text-tree band min-height aligned. - Graph → findings:
jumpToNodeId(id, { scrollRelatedFindings: true })from graph clicks and graph search matches;topRankedFindingForNodepicks the scroll target;graphPivotFindingIddrivesAnalyzeFindingsPanelscroll +pqat-listRow--graphPivot/ From graph cue; auto-clear after a few seconds. - Selected node:
analyze-related-findings-bridge— chips (up to 4) to refocus the ranked list; calm line when no findings cite the node;onFocusFindingInListfor chip clicks. - Text tree:
selectNodeFromTextTreeclears graph pivot (no findings scroll). - Copy trim: Plan workspace intro, graph/text hints, findings intro, rule-reference blurb, graph legend — shorter; duplicate finding list removed from heavy sections (use Ranked list + bridge).
Graph-local findings, no default scroll-jump (Phase 123)¶
- Local shelf:
AnalyzeLocalFindingsShelfunder the graph (and under the text tree) — top 2 ranked findings with title + summary snippet, more<details>with links; explicit per-finding action → setsgraphPivotFindingIdand scrolls the lower Findings list (UI copy: Phase 125). - Graph clicks:
jumpToNodeId(id)— no automatic pivot/scroll; selection updates local shelf in the Plan workspace band. - Selected node: Same shelf (
analyze-detail-local-findings-shelf) after operator title/briefing;rankedFindingsForNodesorts citations. - Ranked list cue: Header Continues from plan +
pqat-listRow--graphPivotwhen opened via an explicit plan-band / detail action (setsgraphPivotFindingId); row subtitle Opened from detail removed when the header band is present (Phase 131).
Cleaner local evidence, dedupe, keyboard/a11y (Phase 124)¶
- Dedupe: When Plan workspace and Selected node are both visible, the full
AnalyzeLocalFindingsShelfstays in the workspace band; the selected-node column usesAnalyzeLocalFindingsBridge(analyze-local-evidence-bridge). If workspace is hidden, the selected-node column still gets the full shelf (analyze-detail-local-findings-shelf). - Shelf navigator: Two preview slots +
<details>for additional ranked titles;role="region", politearia-livecount; per-finding actions setgraphPivotFindingId(visible labels refined in Phase 125). - Selected node: Readout kicker Operator (was “Operator in focus”).
- Copy trim: Plan workspace graph/text hint; Findings intro.
- E2E (Phase 137 name): graph click shows
analyze-graph-issue-summary+ local shelf;#analyze-ranked-findingsis not focused until an explicit open/skip; no Continues from plan until pivot.
Stronger workspace band, smarter bridge, go-deeper cues (Phase 125)¶
- Reading path: Selected-node readout sits in
pqat-investigationThread(one band with the Operator block). Workspace shelf heading Why this operator matters; lead card Strongest match; per-preview Full write-up in list (explicit deeper step). - Bridge:
analyze-local-evidence-bridge— single finding: compact severity + title + Open full finding in list; multiple: Up to \<severity> chip + short count line + Open top in full list (does not outshine the shelf). - Truncation: When more findings exist than the two preview slots,
analyze-local-evidence-truncation-cuestates Showing 2 of N and points to the full ranked list for complete evidence. - Findings column: Intro names Full write-up in list as the deliberate jump from the plan band.
- Tests / E2E: Vitest bridge + shelf; interaction test for bridge-only pivot; Playwright
Analyze: bridge Open full finding shows Opened from detail in ranked list.
Smoother investigation flow, unified evidence language (Phase 126)¶
- Navigation: After local evidence in the plan band,
Skip to Ranked findings(pqat-skipToRanked,href="#analyze-ranked-findings") when the Ranked column is visible; targetidonAnalyzeFindingsPanel. - Unified CTAs:
evidenceNavCopy+ariaLabelFullWriteUpInRankedList/ariaLabelOpenStrongestInRankedListinlocalEvidencePresentation— shelf + bridge use Open in ranked list; multi-bridge uses Open strongest in ranked list;AnalyzeFindingsPanelimportsseverityLabel/severityChipClassfrom the same module; plan guide rail usesseverityLabelfrom there. - Multi-finding shelf: Truncation +
Other titles (N)disclosure; compact hint — full rows in Ranked when previews are capped. - Bridge (multi): Copy strongest first in the band above (ties shelf ordering).
- Narrow:
@media (max-width: 720px)easespqat-investigationThread, shelf, and bridge padding. - E2E: Skip link presence; bridge pivot test renamed to Open in ranked list.
Focus flow, confidence parity, clearer previews (Phase 127)¶
- Arrival: Skip link uses explicit
scrollIntoView+focus()on#analyze-ranked-findings(not hash-only).Open in ranked list/ shelf actions still setgraphPivotFindingId;AnalyzeFindingsPanelscrolls the rankedClickableRowthen retriesfocus()on it (interval untildocument.activeElementmatches or cap; virtual lists get more time afterscrollToIndex). - Scoped DOM: Scroll/focus targets
#analyze-ranked-findings [data-finding-id][role="button"]so plan-band preview<li data-finding-id>rows inAnalyzeLocalFindingsShelfdo not winquerySelectorfirst.scrollToPrimaryFindingRow(Analyze) uses the same scope. - Ranked region:
aria-labelledbyon the panel +h2analyze-ranked-findings-heading;pqat-rankedFindingsPanel:focus-visibleoutline. - Evidence language:
findingConfidenceLabelshared fromlocalEvidencePresentation; shelf preview listaria-labelclarifies preview; Strongest matchtitletooltip; multi-bridgearia-labelties strongest to plan band; pivot finding rowaria-labelincludes Opened from plan detail. - Multi-finding: Truncation Previews N of M · +K under Other titles · Full rows stay in Ranked.
- Shortcuts nav: Skip link wrapped in
nav.pqat-planEvidenceShortcuts. - E2E: Skip link
focus()+Enter(avoids overlapping hit-test flakes); bridge open assertsdocument.activeElementmatches.pqat-listRow--graphPivot(DOM focus contract).
Preview vs ranked semantics, deterministic arrival, focus polish (Phase 128)¶
- DOM: Plan-band preview list items use
data-pqat-preview-finding-idonly; ranked rows keepdata-finding-idonClickableRow. Shared helperqueryRankedFindingRowinanalyzeEvidenceDom.ts(tests cover preview + ranked with the same logical id). - Arrival:
VirtualizedListColumn.scrollToIndexaccepts optionalonSettled(row index appears ingetVirtualItems(), then rAF). Graph-pivot focus uses scroll completion + short rAF retries (nosetInterval). Pivot cue clears 4.2s after focus lands (onGraphPivotFocusArrived); 6s fallback if focus never lands (Phase 129). - Chrome:
.pqat-listRow--graphPivothairline ring +focus-visibleoutline;nav.pqat-planEvidenceShortcuts:focus-withinraises stacking for skip-link pointer use; bridge actionsz-indexnudge.
Ranked arrival cue, virtual settlement tests, skip proof (Phase 129)¶
- Arrival: After open-from-plan focus lands on the ranked row, a compact status line (
pqat-rankedArrivalCue) confirms the landing;Findingsh2usesaria-describedbywhile the cue is visible; panel getspqat-rankedFindingsPanel--arrivalCueemphasis (Phase 130 refines copy, ~2.8s dwell, and styling — see below). - Virtual list:
scheduleVirtualScrollSettledinvirtualizedScrollSettled.tsbacksVirtualizedListColumn.scrollToIndex(..., onSettled); unit tests cover success, bounded fallback, and cancel. - Plan workspace: Graph legend (hot ex / hot reads) sits above the canvas so it does not overlap Skip to Ranked findings; skip link has
data-testid="analyze-skip-to-ranked-findings"; E2E covers keyboard (Enter) and nativeclick()on the anchor (same handler path as an intentional activation). - Pivot fallback: If focus never arrives,
graphPivotFindingIdclears after 6s (safety net).
Calmer ranked continuation, skip pointer E2E, Vitest SVG noise (Phase 130)¶
- Thread hint: While
graphPivotFindingIdis set, the Ranked band shows Continues from plan beside the Ranked eyebrow (pqat-rankedThreadHint),pqat-rankedFindingsPanel--pivotContinuitytint, andFindingsh2aria-describedbyincludes that hint’s id (plus the transient arrival cue when active). Phase 131 drops repeated operator mono + row Opened from detail when this header is shown (anchor stays in the row body). - Arrival cue: Copy “On the matching row — full write-up below.” (~2.8s);
pqat-rankedArrivalCueis a left-accent stripe (not a full card); panel emphasis uses a light inset shadow;prefers-reduced-motiondrops the transition. - Pivot row:
.pqat-listRow--graphPivotkeeps a faint wash after the cue fades so the row stays legible. - Scroll:
#analyze-ranked-findingshasscroll-margin-topfor skip / in-page focus. - Skip E2E:
focus()thenclick()— skip is off-screen until focus expands it, so the test uses a real pointer path on the revealed control. - Vitest:
SVGGraphicsElement.getBBoxshim for.react-flow+ narrowconsole.errorfilter for React DOM’s “Received NaN for the `…` attribute” (jsdom + React Flow);AnalyzePlanGraphCoreuses dots background everywhere (lines were test-only before and still noisy under jsdom).
Phase 132 — narrow lower-band order, ranked region labeling¶
- Narrow:
AnalyzePagerendersvisibleLowerReadingOrder— findings → suggestions → selected node whenlayoutTier === 'narrow'(stacked grid), so ranked + continuation stay ahead of selected-node detail in tab order. - Ranked continuation: Contract
role="region"+aria-labelledby(thread hint + Findingsh2);data-testid="analyze-ranked-handoff-hint"for structural tests.
Phase 133 — ranked region description (pivot band)¶
- Pivot continuity: Inner
analyze-visual-ranked-continuation-contractusesaria-labelledbyon the Findingsh2id andaria-describedbyon the Continues from plan hint id so the thread line is not announced twice via both region andh2when the arrival cue is absent.h2still usesaria-describedbyfor the transient arrival status whenshowRankedArrivalCueis on (Phase 129–130 behavior).
Phase 134 — reopened ranked continuity (saved ?analysis=)¶
analyzeRankedHandoffOrigin:linkafter persistedgetAnalysissuccess,sessionwhen running Analyze from plan text or Clear.- Copy:
analyzeRankedContinuityCopy—Ranked — reopenedbeside Ranked when restored and not in graph-pivot band; pivot band Continues from plan — reopened when restored +graphPivotFindingId(sibling rhythm to Compare Summary — reopened). - a11y:
#analyze-ranked-findingsgainsaria-describedbypointing at the restored band hint id when that hint is visible. - Tests: Vitest + Playwright reopen / pivot / export paths (see
docs/compare-workflow.mdPhase 134 for cross-route E2E notes).
Phase 135 — shared reopened suffix + export parity¶
withReopenedSuffix(reopenedContinuityCopy.ts) backs Ranked — reopened / Continues from plan — reopened with the same convention as Compare pair handoffs.- Export success:
exportDownloadSuccessHintoptionalrestoredFromLinkwhen exporting a snapshot aftergetAnalysis(seedocs/compare-workflow.mdhandoff matrix).
Phase 136 — stable ?analysis= for reopen + E2E¶
AnalyzePage:buildAnalyzeDeepLinkSearchParamssync usesuseLayoutEffect(notuseEffect) so?analysis=is present in the same paint cycle as the summary shell—reduces flake when tests capturepage.url()right after Analyze succeeds.- Playwright:
persisted-flowsusesexpect(page).toHaveURL(/[?&]analysis=/)before persisting the URL on paste→analyze→reopen and ranked-reopen paths.
Phase 137 — graph-first issue explanation (in-band readout)¶
AnalyzeGraphIssueSummary(analyze-graph-issue-summary) sits under the graph / text tree inAnalyzePlanWorkspacePanel, insidepqat-graphInvestigationStack: What looks wrong here (severity + strongest local title), Why it matters (first clause from the finding summary), Inspect next (Selected node + Ranked as optional depth).- Presentation:
graphNodeIssueSummaryPresentation.ts(buildGraphNodeIssueSummary,firstWhyClause) — Vitest unit tests. - Workspace shelf demotion:
AnalyzeLocalFindingsShelfcompactWorkspacePreview— heading More in Ranked, no Strongest match badge, shorter snippets, single-finding workspace omits the duplicate summary paragraph (issue band already states it), CTA Full write-up in Ranked. - Bridge:
AnalyzeLocalFindingsBridge— fewer repeated titles; Ranked actions read as next step, not the primary decode path. - Layout:
pqat-graphInvestigationStack--fillsColumnlets the graph frame share flex height with the new readout in paired mode; default graph min-height slightly lower so the investigation band balances text vs canvas. - E2E: Graph click asserts
analyze-graph-issue-summary+#analyze-ranked-findingsis notdocument.activeElementuntil an explicit open/skip.
First-screen triage deck + downstream continuity (Phase 109)¶
- Bundle:
buildAnalyzeTriageBundlefeedsanalyze-triage-deck: hero Start here, ranked Then scan (1–2–3), primary Open in plan when the takeaway names a node. - Narrow:
stickyTriageNarrowkeeps the triage shell near the top when the workstation stacks. - Continuity: Findings scrolls the Matches summary row into view when it is off-screen (skipped for virtualized long lists); Selected node and Plan guide show a short cue when the selection matches the summary’s primary focus; guide Plain-language readout stays collapsed by default.
- Examples: fourth sample — join-heavy nested loop (
nl-join-inner-heavy) — seesrc/examples/README.md.
Guided-link parity, first-frame polish, help-shell visual contract (Phase 106)¶
- Shared URL open:
openWorkflowGuideWhenUrlRequests(workflowGuideOpenFromUrl.ts) runs fromuseLayoutEffecton Analyze and Compare so?guide=does not flash closed for one frame while preserving the false→open announcer path. - Compare merged E2E:
persisted-flowscopies Compare merged guided link with a decoy query param,gotothe clipboard URL, and expects the guide panel visible—parity with Analyze merged coverage. - Visual contract:
data-pqat-help-visual-contract="1"onWorkflowGuideShell;e2e-visualcapturesanalyze-workflow-guide-panelasanalyze-workflow-guide-shell.png(help chrome vs analysis). - Copy clarity: Footer hint +
title/aria-labelspell out merged (full context) vs entry (clean route).
Local vs reopened analysis: Pasting and analyzing keeps the raw text in the browser until you clear it. After success, the address bar gains ?analysis=<opaqueId> (and node= when a node is selected) so the same result can be fetched again from SQLite-backed storage. Opening a URL with analysis= loads the snapshot from the server (GET /api/analyses/{id}) and clears the textarea; running Analyze again on new paste strips the old analysis query param and replaces it after the new response. Responses include artifactSchemaVersion (Phase 49); the server normalizes older stored JSON on read. If the snapshot is missing, access denied, corrupt (422), or from an unsupported newer schema (409), the page shows a specific message instead of a generic failure. Copy share link (non-auth) or Copy artifact link (auth deployments) reflects server policy—see Deployment & auth. Links remain valid across API restarts if the database file is kept (see API & Reports).
Phase 72 + 84 + 86 (copy reliability + ticket payloads): copyToClipboard tries document.execCommand('copy') on a hidden textarea first (same synchronous turn as the click, so Safari / strict Chromium user-gesture rules still work), then falls back to navigator.clipboard.writeText. useCopyFeedback shows success or an honest copy-failed hint; status lines use role="status" + aria-live="polite" + aria-atomic="true" on copy feedback, with a slightly longer success dwell time for assistive tech (Phase 86). ClickableRow ignores clicks that originate on nested button / a / [data-pqat-row-no-activate] so copy does not flip navigator selection. nodeReferenceText / finding / hotspot copies prepend PQAT analysis: <id> when scope is known; share link copy uses a multi-line block (URL + PQAT analysis: + optional Node:) via analyzeDeepLinkClipboardPayload. Optimization suggestions use suggestionReferenceText (legacy string analysisId or structured context). Playwright smoke records payloads from writeText and execCommand('copy') and asserts node reference, suggestion ticket copy, Analyze share link (PQAT analysis:), suggested EXPLAIN, Compare pair reference, Compare deep link, and Compare suggestion Copy for ticket blocks (Phase 87). Backend PostgresJsonAnalyzeFixtureSweepTests exercises the full PlanAnalysisService pipeline over every top-level fixtures/postgres-json/*.json (see Fixtures).
See Capturing EXPLAIN JSON for recommended commands, share-link behavior, and normalizer caveats.
Analyze workspace (Phase 39 + Phase 42)¶
After Analyze succeeds, the page is organized as an investigation workstation instead of one long vertical stack:
- Input / actions — paste, suggested EXPLAIN, Analyze, exports, optional Sharing when auth is enabled.
- Summary + metadata — compact summary line, Plan briefing (Phase 60–64 + 82 + 83: story lanes use human-first labels—what the plan is doing, where work piles up, what is driving cost, Start here as an ordered list when
inspectFirstStepsis present, else the legacy single paragraph). Step 1 may include Focus in plan when the backend attachedfocusNodeId. The path still points at ranked Optimization suggestions in the guide instead of pasting the top card (Phase 82 dedup). Markdown/HTML exports mirror the structured list when steps exist (Phase 83). When source SQL mentionstime_bucket, the index/shape lane may append a bounded analytical hint (feeds/scans vs finalize hop). Plan source / EXPLAIN metadata and share/artifact copy when applicable. Phase 92: canonicale2e-visualPNG for this story surface usesdata-testid="analyze-visual-summary-contract"(metrics + Plan briefing block only), not the full card—seee2e/visual/README.md. - Plan workspace — the graph (or text tree) is the visual center. Responsive tiers (
useWorkspaceLayoutTier): narrow (<900px) stacks the guide below the graph; medium (900–1319px) keeps graph + Plan guide side-by-side with slightly different column weighting than wide (≥1320px), which gives the graph and investigation surface more horizontal room. Phase 97: on medium/wide when the guide is visible, the workspace grid row usespqat-analyzeWorkspaceRow--pairedso the Plan workspace panel and Plan guide share the same row height (no short capped rail beside a tall graph). The guide usesbesideWorkspacechrome: fixed Companion / Plan guide header,overflow-y: autoon the body; the graph canflexto fill the remaining column height (**graphFillColumn**onAnalyzePlanGraphCore). Narrow keepsstackedrail behavior with a boundedmax-heightscroll on the whole aside. The left column holds plan mode toggles, search boxes, fit/focus/reset, and the tree or graph; the right column (when visible) is the Plan guide rail with: - Focused operator readout when a node is selected: human-readable title, Phase 63–64 backend
operatorBriefingLine, Phase 82 Bottleneck posture strip when this node appears on a ranked bottleneck (primary vs stacked rank + class chip + headline), join/branch subtitle when applicable, metrics, longer What this operator is doing interpretation, findings cue, and a collapsed Technical id for the canonical planner path - Plan narrative — Orientation lane:
planStory.planOverview; What happened lane: clampednarrative.whatHappened; Propagation & flow lane: propagation beats with Focus (humananchorLabel, not a raw path) - Main bottlenecks (Phase 58–62) — triage-style cards: rank + class + kind + cause chips, headline/detail, optional symptom (nested-loop inner or hash-build side when inferable) and propagation line;
humanAnchorLabeldrives Focus. Phase 67:OperatorNarrativeHelperenriches nested-loop bottleneck copy when the inner child shows highActual Loops(especially Seq Scan on the inner relation), and inner-side symptom notes call out repeated inner execution with measured loop counts. Phase 68–71 (Compare-facing): when you compare before/after plans, the same Sort / Seq Scan / Index Scan / bitmap / index-only / aggregate / gather-merge anchors feedregionContinuityHint,regionContinuitySummaryCue, and optionalcontinuityKindKeyon mapped pairs—Analyze copy is unchanged, but saved comparisons benefit from the richer access-path, ordering (including query-text ORDER BY tie-breaks), GROUP BY / time_bucket-bounded SQL hints for grouping continuity, and output-shaping continuity described in Compare workflow. - Where to inspect next — Phase 82: short pointer to Snapshot → Plan briefing → Start here (full numbered text is not duplicated here), then hotspot rows (click/keyboard selects the node; Copy stays separate)
- Top findings — a compact preview (not a replacement for the full list below)
- Next steps — a short preview of the top optimization suggestions; full ranked cards (priority → confidence, Try first styling, Strongest next experiment callout) live in the lower workspace (Phase 82). Cards linked to a bottleneck show a Bottleneck #N chip that jumps to the bottleneck anchor in the plan (Phase 83), plus the Because of bottleneck line when it resolves.
- Source query — optional, folded in a
<details>block in the rail when query text exists
On narrow viewports the rail stacks under the graph so the flow remains a single column.
Phase 98: In besideWorkspace mode, when Selection snapshot is part of the guide (selection in guideSectionOrder), the Focused operator block is rendered in pqat-planGuideRail__stickyBand between the Plan guide title and the scrollable body so the live selection readout stays visible while narrative, bottlenecks, and lower sections scroll.
Phase 99: Text plan mode uses pqat-planTextTreeBand when the workspace is paired with the guide—a bounded-height, bordered scroll surface so the tree occupies the shared row like the graph canvas. The scrollable guide body is named Plan guide — scrollable sections for assistive tech when besideWorkspace. Phase 100: flex: 1 1 0 on the text band improves flex sizing inside the shared paired row.
- Findings, suggestions, and selected node — below the plan workspace, a second band keeps the full findings list on one side and the full optimization suggestions plus selected node detail on the other. The Selected node panel shows the primary label, Phase 63–64
operatorBriefingLinewhen present, then an operator-level What this operator is doing readout whenoperatorInterpretationis present (Phase 59, derived fromOperatorNarrativeHelper), then cues and actions; the heaviest blocks load as a lazy sub-chunk (Phase 45). Plan guide · Main bottlenecks cards (Phase 64) repeat the sameoperatorBriefingLineunder a Briefing kicker when the backend attached it. Optimization suggestions may show a Because of bottleneck line whenrelatedBottleneckInsightIdsresolves to a ranked bottleneck. Within the lazy chunk,<details>still progressive-discloses operator context, workers, raw JSON, and metrics.
The graph panel uses a viewport-relative height when the guide is hidden or on narrow (clamped min/max) so small plans do not leave a huge empty band under the canvas; when paired with the guide, the canvas grows with the shared row instead of stopping short while the rail was previously max-height-capped (Phase 97). Graph behavior (search highlight, collapse, fit, focus, URL ?node= sync, copy reference/link) is unchanged in intent.
Workstation presentation (Phase 55)¶
- Empty capture: “Ready to analyze” lead-in plus short guidance before the first run.
- Errors / reopen: Banners classify access denied, artifact corrupt / schema mismatch (422/409), and generic failures; persisted loads use a dashed loading banner.
- Summary deck: The snapshot card uses a top signal strip (accent gradient),
Outfit+ IBM Plex Sans typography (seeindex.html), and the same metric grid rhythm as before. - Findings: Selected rows prefer a left accent bar (
ClickableRowaccent-bar) instead of only a flat fill. - Suggestions: Try next is the primary signal line when it adds information beyond the summary; chips emphasize priority and confidence, with a scan-friendly action lane (experiment / validate / shape). The top high-priority card is visually highlighted (Phase 82).
- Sharing (auth): Sharing & access is a framed
<details>withpqat-*form controls and an inline info strip summarizingauthHelp+ Vite env hints.
Motion: light fade-in on heavy panels and banners; prefers-reduced-motion: reduce disables shimmer/fade loops globally (index.css).
Appearance / theme (Phase 65–66)¶
- Top bar → Theme appearance: System (default), Dark, or Light. System shows a compact → Dark / → Light hint for the resolved skin and exposes screen-reader text via
aria-describedby. System tracksprefers-color-schemelive (not frozen at first load). - Persistence: localStorage key
pqat_theme_v1. When the API reportsauthEnabled: trueand the SPA is built withVITE_AUTH_*credentials, the same preference string is also stored under/api/me/preferences/appearance_theme_v1(debounced PUT after changes, GET hydrate on load — same mechanism as workspace layout keys). Non-auth deployments stay local-only. - Rendering:
html[data-theme]is the skin selector for CSS;html[data-effective-theme]duplicates the resolved value as a stable test/debug hook. Theindex.htmlboot script sets both before React hydrates. - Readouts: Plan briefing, bottlenecks, selected-node Briefing, story lanes, join badges, and banners use shared
--pqat-*tokens so hierarchy stays clear in both skins. - Testing: Playwright
e2e/theme-appearance.spec.tsruns undere2e-smokeand checksdata-theme-preference,data-effective-theme, reload persistence, andemulateMediabehavior for System.
Visual hierarchy (Phase 43)¶
Regions use a shared workstation style: capture and summary panels read as lighter “chrome”; plan workspace is the elevated investigation surface; findings and selected node use detail panels with clearer typography rungs; severity and confidence appear as chips; the Plan guide rail is visually distinct from the graph column. This does not change analysis behavior or layout persistence.
Delivery & patterns (Phase 44)¶
The Analyze route is code-split (React.lazy + Suspense); the shell shows a short Loading Analyze… placeholder while the page chunk loads. workstation-patterns.css groups dense layout utilities (form grids, capture stack, route fallback) so workstation.css stays focused on core controls. Customize workspace loads the drag-and-drop reorder lists only after you first open the <details> (further shrinking the initial Analyze chunk until customization is needed).
Progressive loading & performance (Phase 45)¶
After the route chunk loads, the page still stages heavy UI:
- Graph vs text — In Text mode, the React Flow bundle is not required; switching to Graph loads
AnalyzePlanGraphCorebehindPlanGraphSkeleton. The tool prefetches that chunk on Graph hover/focus and on idle when you are already in graph mode so the first paint of the canvas is usually quick. - Lower band — Findings, optimization suggestions, and selected node are separate lazy modules with a shared
LowerBandPanelSkeleton(HeavyPanelShell) so capture + summary + plan workspace can appear before those panels’ JS parses. - Long lists — Findings and optimization suggestions use
VirtualizedListColumn(@tanstack/react-virtual) when the filtered list is long (short lists stay a simple column; row heights can grow when Evidence<details>expand). Phase 48: when optimization suggestions are grouped by family, long lists flatten into virtual rows (section header + card) so family subheadings are not skipped;getItemSizesupplies shorter estimates for header rows than cards. - Selected node — The panel header (label, worker cue, index insight, copy actions) renders first; operator context, buffer I/O, workers grid, raw JSON, metrics JSON, and the findings-for-node bullet list live in
AnalyzeSelectedNodeHeavySections, loaded lazily with an inline skeleton.
URL sync (?node=), selection, and copy behavior are unchanged; tests preload graph and lower-band modules in setup.ts so jsdom does not stick on Suspense fallbacks.
Customize workspace (Phase 40 + Phase 42)¶
The Analyze UI is split into typed panels (capture, summary, plan workspace, plan guide, findings, optimization suggestions, selected node). Use Customize workspace (under Plan workspace) to:
- Presets: Balanced (default), Wide graph (hides plan guide rail), Reviewer (findings → selected node → suggestions in the lower band), Focus (hides suggestions region), Detail (suggestions before findings), Compact (hides summary, guide, and suggestions for a denser tree-first view).
- Visibility: show or hide each major panel. If plan capture is hidden, a Show input strip appears so you can recover it.
- Plan guide section order: drag the handle or use Up/Down to reorder blocks (selection snapshot, what happened, main bottlenecks, hotspots, top findings, next steps, source query).
- Lower band column order: same drag + Up/Down pattern for findings, optimization suggestions, and selected node; wide screens show columns side-by-side, medium/narrow wrap or stack by tier.
Persistence: layout is stored in localStorage under pqat.analyzeWorkspaceLayout.v1 (versioned JSON). When the API reports authEnabled and the SPA is built with VITE_AUTH_API_KEY or VITE_AUTH_BEARER_TOKEN, the same layout is loaded from and saved to GET/PUT /api/me/preferences/analyze_workspace_v1 after hydrate (debounced saves). If auth is off or the request fails, local layout still applies.
Implementation reference: src/frontend/web/src/analyzeWorkspace/*, src/frontend/web/src/components/analyze/*, slim AnalyzePage.tsx orchestration.
Reading the results¶
Hotspots (“Where to inspect next”)¶
Hotspots are rendered as clickable items that select the corresponding node. On desktop they also appear in the Plan guide rail beside the graph (see above) so guidance stays next to the plan view.
- exclusive runtime: local operator work dominating time
- subtree runtime: work dominated by descendants
- shared reads: I/O hotspots (requires
BUFFERSin the input JSON)
PostgreSQL includes buffer counters as flat properties on each plan node in JSON (for example "Shared Read Blocks", "Temp Written Blocks"), not only under a nested "Buffers" object. The parser accepts both shapes. Per-worker buffer lines under a "Workers" array are merged onto the parent node when the leader omits those totals, so summary.hasBuffers and read hotspots can still light up for parallel plans. The same Workers entries are also preserved as a typed workers list on that node in the API response (per-worker timing, rows, loops, and buffer counters), separate from the parent’s aggregate fields when PostgreSQL provides both.
Each hotspot row also includes a subtle Copy action that copies a concise, human-readable node reference (optionally annotated as a hotspot).
Interaction model: the row is one keyboard-focusable target (click or Enter/Space) to select the node. Copy is a separate button so the row is not nested invalid markup.
Findings¶
Findings are ranked by severity/confidence and tied to nodes. Use the node anchor label to jump into the plan tree.
Finding rows also include a subtle Copy action that copies a concise human-readable reference for the anchored node (optionally suffixed with the finding title).
Interaction model: same as hotspots—the row selects the anchor node; Copy is a separate control.
Index tuning: findings and structured indexInsights distinguish (a) likely missing / worth-investigating index on selective seq scans (F, J), (b) index or bitmap path still heavy (R), (c) bitmap recheck review (S), (d) chunked bitmap + Append workloads where indexes likely exist but aggregate I/O stays large (P), (e) nested-loop inner index alignment (Q, and E for high amplification), and (f) sort hotspots with optional order/index hints (K). None of these assert a specific DDL.
Optimization suggestions (Phase 32 + Phase 47)¶
Findings explain what looks wrong and attach evidence. optimizationSuggestions are a separate, ranked list of investigation-oriented next steps: index experiments, query-shape or ordering ideas, statistics maintenance, join/hash/sort volume reductions, parallelism skew checks, Timescale/chunk workload guidance, and explicit “observe before change” validation.
- Suggestions are evidence-linked (
relatedFindingIds,relatedIndexInsightNodeIds,targetNodeIds) and includesuggestionIdvalues with thesg_prefix (deterministic content hash). Compare-only payloads may also includerelatedFindingDiffIds/relatedIndexInsightDiffIdson suggestions. Fields include confidence, priority, cautions, and validation steps. - Phase 47 — human-readable payload: each suggestion also carries
suggestionFamily(UI grouping: index experiments, query shape & ordering, statistics & planner accuracy, schema & workload shape, operational tuning & validation),recommendedNextAction(short imperative),whyItMatters(plain impact), optionaltargetDisplayLabel(human operator/relation phrasing—avoid raw node paths in primary copy), andisGroupedClusterwhen multiple overlapping statistics-style findings were consolidated into one card. Title and summary stay user-facing; details / rationale hold denser evidence. - They are not prescriptions: the tool does not emit guaranteed
CREATE INDEXDDL. Language stays conservative (especially for P chunked-bitmap plans, where naive “add another index” advice is suppressed in favor of window pruning, ordering, aggregates, and retention-style investigation). - The Plan guide rail shows a preview of top suggestions (family · confidence · priority, summary, Next · line when present, Focus … using
targetDisplayLabelwhen the API provides it). The full list in the lower band uses separate metadata chips (readable phrases like “High confidence”, notConfidence: highjammed into titles). Long lists may show family subheadings when that improves scanability; validation lines are phrased as experiments, not boilerplate. Phase 48:normalizeOptimizationSuggestionsForDisplaybackfills missing Phase 47 fields when reopening older stored analyses (family inferred from category, Next / Why from validation/rationale,targetDisplayLabelfrom the first target node id when absent)—no forced re-analyze. - The Selected node panel may show the strongest related suggestion when any suggestion lists that
nodeIdintargetNodeIds(title, summary, and Next · when present).
Plan index posture (summary card)¶
After analyze, the summary card can show an Index posture line from indexOverview: scan mix (seq vs index vs bitmap counts) or, for Append + many bitmap heaps, an explicit note that per-chunk index use may already be happening while total reads/temp work can still dominate.
Selected node¶
The selection snapshot in the Plan guide rail gives an immediate, compact readout when you click the graph, hotspots, findings, or suggestions. The full selected node panel (lower section) shows:
- human-readable label
- join/branch subtitle when applicable
- optional side-aware context line when supported by evidence
- Copy reference (human-readable node text) and Copy link (full URL with
?node=<nodeId>for the current selection). Phase 34: the address bar stays in sync: selecting a node updates?node=(replace history, deduped), loading a URL with a validnode=restores selection after analyze, and browser back/forward updates the selected node when the id still exists in the current result. - Related optimization suggestion (when a ranked
optimizationSuggestionsentry targets thisnodeId): title, summary, and optional Next · line, to avoid duplicating the full suggestions list - Access path / index insight (when
indexInsightsentries target thisnodeId): compact headline, access-path family, and signal kinds (investigation-oriented, not prescriptions) - Buffer I/O: when this node has any shared/local/temp buffer counters in the API payload (including explicit zeros), a short labeled list is shown above the raw JSON dump; if the plan has buffers elsewhere but this operator has none, a one-line hint explains that
- Workers (parallel plans): when the node has a non-empty
workersarray, a one-line worker summary (count and conservative read/time/temp cues when applicable) plus a compact per-worker grid (worker id, total time, rows, shared hit/read, temp read/write). Parent row buffer/timing fields remain the leader aggregate when PostgreSQL reports them; worker rows are the explicit slice fromEXPLAINJSON, not double-counted into summaries. - key raw fields and derived metrics
Limits and honesty¶
- Missing timing reduces time-based hotspot fidelity; missing buffer counters reduce read-based hotspots.
summary.hasBuffersis true when any parsed shared/local/temp buffer field is present on any node (null means absent; zero still counts as present). - Query text is passed through and displayed; the tool does not claim exact SQL-to-plan mapping.
After Analyze, the compact summary line under the input includes node count, max depth, severe findings count (from the backend summary), whether actual timing and buffers were present, summary.plannerCosts (detected from JSON: present / notDetected / mixed / unknown), and any warnings when the engine reports limitations. When the request used planText, the API may include planInputNormalization; the UI shows a one-line note (Parsed raw JSON directly vs Normalized pasted QUERY PLAN output). Copy actions use share-link wording in non-auth mode and artifact link wording when the server reports auth enabled; invalid or missing analysis= links show a clear error (including access denied in auth mode). When auth is enabled, an optional Sharing panel can adjust scope and link access (owner-only). Phase 38: production deployments should use JwtBearer (owner = JWT sub) or ApiKey (mapped user id); legacy BearerSubject stores the entire bearer string as owner—see Deployment & auth.
Plan source / EXPLAIN metadata (Phase 34)¶
Below the summary line, a short Plan source / EXPLAIN metadata block lists:
- whether source query text was provided
- planner costs detection (independent of declared EXPLAIN options)
- declared options from
explainMetadata.optionswhen the client sent them - optional recorded EXPLAIN command text when provided
This is separate from findings: it documents how the plan was captured, not what the plan did wrong.
Graphical tree view¶
Use the Graph toggle under “Plan tree” to view the execution plan as a readable, pan/zoom-able tree in the left column of the plan workspace (full width on small screens). Nodes are clickable and stay in sync with:
- hotspot clicks
- findings list clicks
- selected node detail panel
Graph controls¶
- Fit: fit the whole plan into view
- Focus: center on the currently selected node
- Reset: reset viewport to default
Graph search (highlight, not filter)¶
Use “Graph search” to highlight nodes by operator/relation/index text:
- matches are highlighted
- non-matches are dimmed
- use prev/next to jump between matches (updates selection + focus)
- a match list appears under the search box; click an entry to quick-jump
- if a match is hidden under a collapsed branch, the graph auto-expands the necessary ancestors
Subtree collapse/expand¶
Click the ▾ control on a node to collapse its descendants. Collapsed nodes remain visible and marked; descendants are hidden until expanded.