saropa_drift_advisor 4.4.1
saropa_drift_advisor: ^4.4.1 copied to clipboard
Debug-only HTTP server that exposes SQLite/Drift table data as JSON and a minimal web viewer. Use from any Drift (or raw SQLite) app via an injectable query callback.
Changelog #
Introduction #
This changelog is for Saropa Drift Advisor: the Dart package that wires up Drift’s debug server and web viewer, plus the Drift Viewer extensions for VS Code and Open VSX.
Releases are listed newest first. Each version’s opening paragraph sums up what changed for users and ends with a log link to this file at that release’s tag on GitHub.
Install the library from pub.dev; report issues and browse source on GitHub.
Links #
pub.dev — pub.dev / packages / saropa_drift_advisor
VS Code marketplace - marketplace.visualstudio.com / items ? itemName=Saropa.drift-viewer
Open VSX Registry - open-vsx.org / extension / saropa / drift-viewer
Repo - github.com / saropa / saropa_drift_advisor
4.4.1 #
Anomaly scans are now vastly faster, auth-protected servers work seamlessly with discovery probes, and you can globally suppress diagnostics by column name. log
Added #
- Suppress a diagnostic by column name across every table — a new
driftViewer.diagnostics.columnNameExclusionssetting silences a rule wherever a given column name occurs, without listing everytable.columnpair. Nullable-by-design columns that recur across a schema (lastModified,updatedAt) no longer need a// drift-advisor:ignoreon each table that carries them. Matching is case-insensitive. - New diagnostic:
unreachable-ignore-directive— warns when a// drift-advisor:ignoredirective has no code line left to target (e.g. the column or table it was suppressing was since deleted or moved). Previously such a directive silently suppressed nothing, with no indication anything was wrong.
Improved #
- Anomaly scan collapses ~2 000 serial queries into ~5 per table — NULL counts, empty-string counts, and outlier pass-1 aggregates are now folded into a single combined SELECT per table; variance queries are similarly combined. A 40-table, 15-column schema drops from ~2 000 serial full-table scans to ~200, eliminating the multi-minute hang that wedged every other endpoint. Tables with a primary key skip the duplicate-row check entirely (the answer is provably zero). BLOB columns are excluded from the DISTINCT projection. A per-statement timeout and a 60-second wall-clock budget prevent any single scan from blocking the connection indefinitely.
- Anomaly-scan truncation surfaces through the full stack — when the 60-second wall-clock budget expires mid-scan, the partial-result flag now propagates from the Dart server through
/api/issues, the HTML report, the Drift Health panel (as a warning banner), and the Suite Findings dashboard widget (as a ⚠ badge next to the total count, readable by screen readers, not hover-only). Previously a truncated scan returned partial results silently.
Fixed #
drift-advisor:ignorefalse positive with multi-line rationale — a// drift-advisor:ignore code -- rationalewhose rationale wrapped onto continuation comment lines failed to suppress the diagnostic. Two independent fixes: the directive regex now tolerates arbitrary rationale text (:, parentheses, etc.) by stripping everything after the--/—/–separator; and the target-line resolver now skips comment-only lines — both//and/* ... */styles — between the directive and the code it targets.- Auth-protected servers invisible to Saropa Lints discovery —
GET /api/healthwas blocked by the auth gate, so unauthenticated probes (e.g. Saropa Lints integration) treated an authenticated server as absent. Health is now exempt from auth; when credentials are not supplied, it returns a reduced payload (ok,version,schemaVersion,authRequired: true,authScheme) that leaks no internal configuration, plus aWWW-Authenticateheader naming the configured scheme. The full payload is returned when authenticated, andGET /api/also carriesauthRequired/authSchemeonce authenticated.authScheme("basic"or"bearer") lets a client pick the rightAuthorizationheader without guessing or parsingWWW-Authenticate. A 401 on any other endpoint now also always carriesWWW-Authenticate(previously only when Basic auth was configured), so a server challenges identically whichever endpoint is hit. Documented indoc/API.md. Also fixed a related pre-existing edge case: passing empty-stringbasicAuthUser/basicAuthPassword(accepted by validation as "neither set") previously required auth on every request while no request could ever authenticate; it is now treated as auth disabled, matching how an emptyauthTokenalready behaved. doc/API.md— documentslowThresholdMs— theGET /api/analytics/performanceendpoint accepts an optional?slowThresholdMs=<int>query parameter (default 100) and echoes it in the response, but neither was documented; theslowQueriesdescription hardcoded "100 ms" as if the threshold were fixed.doc/API.md— stale implementation pointer — the SQL-from-query-string reference pointed at the pre-migrationassets/web/app.jsinstead ofassets/web/sql-runner.ts.doc/API.md— undocumented/api/issuesfields — the envelope'struncatedflag and thescan_skippedissue type (both live since the anomaly-scan truncation work above) were never added to the endpoint's reference tables.
Internal #
- Activation event listeners now register their disposal handles — three phase-10 subscriptions (server-active-change, discovery-servers-change, generation-change) previously discarded the
DisposableVS Code returns instead of pushing it ontocontext.subscriptions, unlike every other listener in the same file. Fixed for consistency and to close the gap in case activation ever re-wires (hot-reload, multi-root workspace). extractTruncatedFlagshared helper — the Drift Health panel and the Suite Findings dashboard widget each re-derived the sameenvelope.truncated === truecast independently; both now call one exported helper insuite-diagnostics.tsso a future change to the envelope's truncation contract only needs updating in one place.
4.4.0 #
A SQL box now lives in the sidebar, so quick queries no longer need the full notebook panel. log
Added #
- Run SQL straight from the sidebar — a new SQL Console section sits under the toolbox with a statement box and an Execute button, for quick lookups that did not justify opening the full notebook panel. Results open beside the editor as CSV, except a single-value result, which appears inline in the sidebar itself.
- Statements are checked as they are typed — an icon beside the box reports whether the statement reads, writes, or will be refused, and names the reason in plain language before anything is sent. Execute stays disabled for a statement the server would reject, so a bad query costs nothing to discover.
- Writes ask first, reads never do — INSERT, UPDATE, DELETE, and REPLACE prompt for confirmation before running, controlled by a checkbox in the section and by the new
driftViewer.sqlConsole.confirmDestructivesetting (on by default). SELECT statements never prompt, however complex they are. When the connected app runs without write support, the console explains that rather than failing silently. A clipped result set is always announced. - Query history remembers the last 20 statements — a collapsible History panel below the output area lists previously executed SQL. Click any entry to repopulate the text box; clear the list in one click. History persists per workspace and survives editor restarts.
Fixed #
- FTS5 shadow tables no longer flagged as extra — tables like
notes_fts_dataare now recognized as engine-owned when 3+ FTS shadow siblings exist alongside the parent virtual table, preventing false positives on user tables whose name happens to share a prefix (e.g.user_data). - DateTime column type respects build.yaml —
store_date_time_values_as_textis now read frombuild.yamlat diagnostic time, soDateTimeColumnmaps to TEXT when the Drift option is enabled instead of always reporting INTEGER. - Data-skew threshold adapts to table count — an adaptive formula replaces the fixed 50% threshold, and engine-owned tables are excluded from the denominator so their rows no longer inflate the baseline.
- Anomaly checker reads structured fields first —
anomaly.tableandanomaly.columnare preferred over regex extraction from the message string, with the regex retained as a fallback. - Raw SQL bind parameters no longer misidentified —
$,:, and@prefixed parameters are tokenized as their own kind, and${...}Dart interpolations are blanked so embedded identifiers don't trigger unknown-column diagnostics. - Files with raw SQL calls are no longer skipped —
customSelectandcustomStatementcalls now qualify a file for diagnostics even when it declares no Drift table classes. - Named column overrides no longer flagged as mismatches — columns using
.named('...')sethasNamedOverride, and the naming provider skips the getter-table-mismatch check when the override is present. - Keywords inside SQL literals no longer trigger false positives — the SQL validator now masks string literals and quoted identifiers before checking for mutation keywords and stacked-statement semicolons.
4.3.2 #
Extension no longer opens every Dart file as a live document during source lookups, and server switches no longer fork duplicate poll chains. log
Fixed #
- Switching servers no longer forks duplicate poll chains — each server switch could leave an orphaned generation-poll loop running against the previous server, progressively multiplying network traffic and tree refreshes for the rest of the session. Stopping now also cancels the in-flight HTTP request immediately instead of waiting for the server's long-poll timeout.
Added #
-
Dart source lookups no longer open every file as a TextDocument — Go-to-Definition (F12), tree navigation, and badge refresh used to create a live document for every scanned
.dartfile, firingonDidOpenTextDocumentinto every other extension and promoting each file with the Dart analysis server. Now reads raw bytes instead, and the exclude glob skips generated code, build output, dot-prefixed directories (.fvm, .git, etc.), and vendored packages. -
Go-to-Definition results are now cached — repeated F12 presses on the same table or column name return instantly instead of re-walking the entire workspace each time. A file-system watcher invalidates the cache when any
.dartfile is created, changed, or deleted.
Internal #
--dry-runfor translate mode —--run-mode translate --dry-runshows per-locale key and word counts plus the engine that would be used, without loading models or making API calls. Useful for estimating time before a run.- Translation engine no longer holds the Qwen model resident for 30 minutes after a run finishes; the model is explicitly unloaded when the translate pass completes and the default keep-alive is reduced from 30 to 5 minutes.
- Added
qwen_engine.unload()— sendskeep_alive: 0to Ollama to evict the model immediately rather than waiting for the timeout. keep_aliveis now configurable viaSAROPA_QWEN_KEEP_ALIVEenv var.actions.translate_pass()callsunload()in itsfinallyblock.GenerationWatchernow uses a monotonic_pollIdepoch to retire in-flight polls on stop, preventing duplicate chains and stale-server generation writes. Addeddispose()method that permanently stops polling, bumps the epoch, and clears listeners — the extension's deactivation subscription now callsdispose()instead ofstop()to prevent stale-reference restarts.stop()now aborts the in-flight generation HTTP request viaAbortControllerso the network connection is freed immediately on server switch instead of waiting up to 30 s for the long-poll to expire.AbortErroris silently discarded (not counted as a consecutive error). Listener iteration uses a snapshot so a listener callingstop()mid-iteration cannot corrupt the loop.- New shared
dart-source-reader.tsmodule:readSourceText()reads file bytes viafs.readFile(with dirty-buffer fallback),positionFromOffset()computes line/character from raw text, andDART_SOURCE_EXCLUDE_GLOBis the single-source-of-truth exclude pattern for all bulk Dart scans. - Bounded-concurrency bulk reads — badge-refresh table-file scan now reads Dart files in batches of 20 via
readSourceTextsInBatches()instead of serially awaiting each file, reducing wall-clock time on large workspaces. - Locator cache watcher-triggered invalidation is now tested end-to-end: the VS Code mock's
createFileSystemWatchercaptures listener callbacks so tests can simulate create/change/delete events and verify cache eviction. - Architecture invariant test — new test enforces that converted bulk-scan sites stay clean and detects any new site that uses the anti-pattern without being tracked, preventing silent regressions.
positionFromOffset()now clamps negative and past-end offsets to the valid range instead of producing garbage positions from failed lookups.
4.3.1 #
Fixed a crash where large projects could make the extension run out of memory and get killed by VS Code. log
Fixed #
- Large workspaceState causing VS Code OOM crashes — heavy stores (branch snapshots, schema timeline, schema cache, analysis history) now persist to
storageUrion disk instead of the in-memoryworkspaceState. On large projects the extension was accumulating 4.6 MB of serialized state in the renderer heap, triggering VS Code warnings and eventual OOM kills during debug sessions. Existing data migrates automatically on first activation after upgrade. Disk-backed reads are lazy-loaded per key to keep activation fast. A background check now also flags any individualworkspaceStatekey that grows past 100 KB, so a future store won't silently reproduce this bug.
4.3.0 #
The ignore directive for n-plus-one warnings now works even when the diagnostic points at the caller, not the table file. log
Changed #
- Minimum VS Code version raised to 1.134 (August 2026). Users on older VS Code versions will need to update.
- Consolidated docs into a single
doc/folder. MovedDESIGN_LANGUAGE.md,IDE_ONLY_CAPABILITIES.md, andLAUNCH_TEST.mdfromplans/guides/intodoc/. MergedLOG_CAPTURE_FILE_CONTRACT.mdintoEXTENSION_API.mdas a "File-based access" section. - All
doc/*.mdfiles now listed in the README Documentation table. AddedEXTENSION_API.md,DESIGN_LANGUAGE.md,IDE_ONLY_CAPABILITIES.md, andLAUNCH_TEST.md. A parity check script (scripts/check_doc_readme_parity.py) catches future drift.
Fixed #
-
Table pagination now actually pages. The Tables view built its request with malformed page-size and offset parameters, so the server ignored both and returned the same first 200 rows no matter which page you asked for — while the pagination bar still reported "Showing 201–400 of N". Changing the rows-per-page was equally inert. Both the Tables view and Search now build the request through one shared helper, so the two cannot drift apart again.
-
Toolbar icons stay readable when Google Fonts is unreachable. The viewer inlines its stylesheet and script so it works with no network, but every icon was a ligature drawn from a font that only existed on Google's CDN. Offline — an air-gapped machine, a phone-hosted server over
adb forward, or a proxy that blocks Google — the font never arrived and the browser painted the raw ligature names, so the toolbar became a row of clipped words likehomeandtable_chart. The viewer now detects that the icon font failed to load and falls back to the short text label each button already carries, and the font request no longer flashes ligature names while it is still in flight. -
Tab close buttons and table pin buttons are no longer nested inside another control. Each tab placed its
×button inside the tab button itself, and each sidebar row placed its pin button inside the row link. Screen readers responded inconsistently — announcing a tab as "users ×", reading two nested buttons, or dropping the inner one entirely — and Firefox and Chromium disagreed on whether the inner button could be focused at all. Both are now siblings of the control they sit on, so the accessible name and keyboard order are correct and identical across browsers. The layout is unchanged. -
"Confirm before navigating away" now does something. The Settings toggle stored and re-displayed its state but nothing ever read it: the prompt appeared whenever a cell edit was unsaved and never otherwise, regardless of the setting. Turning it off now genuinely suppresses the prompt, and the setting is read at the moment you navigate away, so changing it takes effect immediately rather than after a reload.
-
Example chips in the "Ask in English" panel use the correct monospace face. They asked for a design-system token that does not exist and silently fell back to the browser's generic monospace, so they did not match the SQL preview beside them. Also removed three other references to a font the viewer never loads.
-
Cross-origin web pages could no longer silently trigger writes on the debug server. Only
POST /api/sqlchecked the request'sContent-Typeheader; every other mutating endpoint (/api/import,/api/cell/update,/api/edits/apply,/api/indexes/apply,/api/snapshot,/api/session/share,/api/monitoring,/api/change-detection,/api/activity/capture) decoded the body as JSON regardless of its declared type. Becausetext/plainis a CORS-safelisted content type, a cross-origin HTML<form enctype="text/plain">could be crafted to submit a body that happened to be valid JSON and reach these endpoints with no browser preflight and no user interaction. Each of these endpoints now rejects a request whose Content-Type is notapplication/jsonwith415 Unsupported Media Type, which forces the browser to issue a real CORS preflight for the request — one the server has no route for, so the cross-origin request is blocked outright. -
SQL-format import no longer executes arbitrary DDL/PRAGMA against the connected database.
POST /api/importwithformat: "sql"passed every statement straight towriteQuerywith no validation —DROP TABLE,ATTACH DATABASE,PRAGMA journal_mode, and cross-table DML all executed silently. Every other write path in the server gates statements throughSqlValidator; this one was the outlier. Each statement is now validated withisSingleDataMutationSqlbefore execution; anything that fails (DDL, multi-statement, non-DML) is collected into the error list and skipped. -
Web viewer
esc()now escapes quotes, closing an XSS vector in every attribute-context call site. The HTML escaper used by ~325 call sites across the web viewer escaped&,<,>but not"or'— thetextContent→innerHTMLDOM round-trip never escapes quotes in text nodes. A database cell value containing"broke out ofdata-raw="..."/title="..."attributes, and the remainder was parsed as new attributes — including event handlers. The escaper now uses explicit string replacement for all five HTML-significant characters, matching the extension's canonicalescapeHtmlinshared-utils.ts. -
Generated
ADD COLUMNmigrations no longer corrupt existing rows in non-TEXT columns. TheGenerate Migrationquick fix always backfilled a new non-nullable column withNOT NULL DEFAULT ''. SQLite's type affinity does not coerce that empty string into a number: an INTEGER, REAL, or BLOB column ended up storing TEXT in every pre-existing row, and Drift threw a cast error the first time the table was read — after the migration had already "succeeded." The generator now picks a default matching the column's own type (0for INTEGER/BOOLEAN,0.0for REAL,x''for BLOB,''for TEXT), adds a// TODOreminder that the value is only a sentinel, and falls back to no default plus a// TODOfor any unrecognized type rather than guessing. -
Snapshots and mutation capture no longer OOM-crash the connected app on tables with BLOB columns.
POST /api/snapshot, snapshot compare, and the/api/mutationsbefore/after row capture all read tables with an unboundedSELECT *— every BLOB byte of every row was pulled into memory and JSON-encoded as an integer array. On a table with image/attachment BLOBs this could exhaust the app's native heap and abort the process (SIGABRT), the same failure mode the VS Code extension fixed for its own capture sweeps in v4.1.17. The Dart server now projects BLOB columns as their byte length (length(col)) instead of their bytes, and caps captured rows at the same limit already used for ad-hoc SQL results. -
Adding a row to a table with a TEXT, UUID, or composite primary key no longer drops the key. The "add row" validation assumed every primary key was an auto-generated
INTEGERid and stripped it from the INSERT unconditionally, so a user-typed TEXT/UUID key (or either half of a composite key) was silently discarded and the row was written with a NULL key that could never be edited or deleted afterward. Only a loneINTEGER PRIMARY KEYcolumn (SQLite's rowid alias) is still auto-omitted when left blank; every other primary key column is now included in the INSERT when supplied, or the insert is rejected with a message asking for the missing key. -
Generation long-poll no longer aborts on an idle database. The
/api/generationendpoint blocks server-side for up to 30 s, but the client was using the default 8 s fetch timeout — every idle poll was aborted, triggering exponential backoff and eventually tripping the circuit breaker. Now uses the same 31 s long-poll timeout as/api/mutations, extracted into a sharedLONG_POLL_TIMEOUT_MSconstant so the two endpoints cannot drift apart. -
drift-advisor:ignoredirectives now work when diagnostics are pinned to caller sites. Previously,n-plus-oneandslow-query-patternignore directives in a table definition file were only consulted when the diagnostic was anchored to that same file; when a caller location was available (the common case), the suppression was silently skipped. Both file-level (ignore-file) and field-level (ignore) directives in the table file are now honoured regardless of where the diagnostic is pinned. -
Tables declared with a
with <Mixin>orimplements <Interface>clause are no longer invisible to every diagnostic. The Dart source parser's table-class detector required the opening{to followTableimmediately, so Drift's documented pattern for sharing columns via a mixin (class Contacts extends Table with TimestampMixin {) was never recognized. Affected tables produced zero schema/naming/primary-key diagnostics, were skipped by migration generation and schema diff, and were additionally reported as a false-positiveextra-table-in-dbbecause the parser had no record that the table existed in Dart. The class-header pattern now allows optionalwith/implementsclauses (including formatter-wrapped line breaks) betweenTableand{. -
Generated
CREATE TABLEmigrations no longer drop a table's primary key when it comes from aprimaryKeygetter override.Generate Migrationderived a column's primary-key status solely from.autoIncrement(), so a natural or composite key declared via@override Set<Column> get primaryKey => {...}(the standard Drift idiom for join tables and UUID-keyed tables) was invisible to it — the generated table had noPRIMARY KEYclause at all, so duplicate rows became insertable. The Dart parser now reads this override and the migration generator emits a table-levelPRIMARY KEY (...)constraint for it, or a// TODOreview comment (never a silent guess) when one of its columns can't be resolved. -
"Create all indexes" in the VS Code extension can now actually create indexes. The command posted
CREATE INDEXstatements to the read-only SQL endpoint, which rejects all non-SELECTSQL — every index failed silently, reporting "Created 0 index(es), N failed" with no reason. It now uses the same preview/apply endpoints the browser viewer already relies on, shows accepted vs. rejected statements with the server's reason before anything is written, and reports a before/afterEXPLAIN QUERY PLANcomparison per created index in a new "Saropa Drift Advisor: Index Apply" output channel. -
Editing a 64-bit INTEGER cell (a snowflake/Discord ID, an
Int64Column, or a microsecond timestamp) no longer silently corrupts the stored value. Inline cell edits and new-row inserts converted INTEGER text through a JavaScriptnumber, which only carries 53 bits of exact integer precision; a value above2^53(e.g.9007199254740993) was rounded to the nearest representable double and written to the database with no warning. Values aboveNumber.MAX_SAFE_INTEGERare now kept as an exact digit string and written into the generated SQL unquoted, so the 64-bit value round-trips exactly; a one-time informational message tells you when this path was used.
Internal #
-
CSRF gate coverage script now detects regex drift. The pre-commit check that enforces
_rejectNonJsonBodyon every POST route could silently pass if the router was refactored to check the method differently (variable alias, string literal, route table). A secondary detector now flags any POST-related pattern the primary regex does not recognise, failing the build instead of silently skipping the route. -
Icon font fallback verdict now persists across page loads. The inline
<head>script that was supposed to skip the 3-second blank-glyph period on repeat visits to a known-iconless machine was reading a localStorage key that nothing ever wrote. The probe'sapply()function now persists each verdict ('1'or'0'), a matchingICON_STATE_KEYconstant is the single source of truth for the key name, a parity test asserts the Dart-side inline script reads the same literal, and the pre-commit gate (Gate 7) verifies the key match plus the write-side call in both source and built bundle. -
SQL import validator now accepts SQLite conflict-clause and UPSERT syntax.
REPLACE INTO,INSERT OR REPLACE INTO,INSERT OR IGNORE INTO,UPDATE OR {clause},INSERT ... ON CONFLICT DO UPDATE(native UPSERT), andINSERT ... ON CONFLICT DO NOTHINGwere wrongly rejected as non-DML by the same validator that gatesPOST /api/importand batch edits. Also fixed:UPDATEwith a quoted table name (e.g.UPDATE "Users" SET ...) was rejected because the regex demanded a word-boundary after the verb, which fails when the tokenizer masks quoted identifiers to a non-word placeholder. TheREPLACE()string function inside INSERT/UPDATE/DELETE values or WHERE clauses no longer triggers a false-positive rejection. Rejection error messages now truncate long SQL to 120 characters to avoid leaking schema in HTTP responses. -
Cross-language long-poll timeout guard. Added bidirectional doc comments linking
ServerConstants.longPollTimeout(Dart) andLONG_POLL_TIMEOUT_MS(TypeScript) so a future change to one side surfaces the need to update the other. Newscripts/check_longpoll_timeout_sync.pyparses both constants and asserts the client timeout exceeds the server timeout by at least 1 s — a build-time enforcement of the cross-language contract. Test asserts the TypeScript constant exceeds the server's 30 s window. -
CSRF gate coverage enforced in pre-commit hook. New
scripts/check_csrf_gate_coverage.pyscans every POST route inrouter.dartand asserts it calls_rejectNonJsonBody()or carries a// csrf-exempt:comment. Wired into the Husky pre-commit hook so an ungated endpoint fails the commit. Routes that legitimately skip the gate (bodyless toggles, handlers with internal Content-Type validation) are annotated inline — no line-number bookkeeping. -
Cross-language pagination parameter sync guard. New
scripts/check_query_param_sync.pyparses thelimit/offsetquery parameter names from bothServerConstants(Dart) andbuildTableDataUrl(TypeScript) and asserts they match — preventing a repeat of the bug where the two sides used different parameter names and the server silently ignored pagination. -
Web build freshness gate. New
scripts/check_web_build_freshness.pycomputes SHA-256 hashes of every.ts/.js/.scsssource and both generated artifacts (bundle.js,style.css), then compares against a committed manifest. Detects uncommitted build drift — a source edit without a correspondingnpm run build— before the commit lands. Wired into pre-commit as Gate 5. -
Design-token and l10n placeholder parity gate. New
scripts/check_reference_parity.pyruns two checks: (1) everyvar(--token)in SCSS resolves to a real--token:declaration or an allowlisted gap, and (2) every{n}placeholder in English l10n catalogs appears in every locale's translation. Uses a shrink-only baseline (scripts/l10n_placeholder_baseline.json) for the 330 known-bad placeholder pairs documented in bug 085, so new damage fails the gate while existing debt is tracked. Wired into pre-commit as Gate 6. -
Accessibility regression tests for tab close and table pin buttons. New
assets/web/test/a11y-tab-close-pin.test.mjs(15 tests) covers tab close buttonrole/aria-label, tab buttonaria-selected, pin buttonrole/aria-pressed/accessible name, pin iconaria-hidden, sibling structure (pin not nested inside table link), and the acceptedaria-required-childrenviolation in the tablist (documented as explicit test suppression). -
Bug 085 repair roadmap. Full parity analysis revised the damage count from 108 sentinel-grep hits to 330 broken strings across all ten locales (not seven). Categorized into three repair phases: 86 sentinel-residue strings (regex-fixable), 232 placeholder-dropped strings (human review), and 12 hallucinated strings (re-translation). Per-locale breakdown and regex pattern documented in the bug report.
-
L10n parity gate hardened. The English-catalog parser now accepts both single- and double-quoted TypeScript string literals. MT-residue regex anchored with word boundaries to eliminate false positives on ordinary words.
--strictflag added to fail the gate on baseline entries too (for CI enforcement of baseline shrinkage). Runtime-set token scan extended to.jsfiles. -
Guarded all remaining unprotected
localStorageaccess. Seven call sites acrosstheme.ts,session.ts,persistence.ts,settings.ts, andtoolbar.tsread or wrotelocalStoragewithout a try/catch, which throws in private-mode or restricted webview contexts. All sites now catch and degrade silently — the UI still functions, it just loses persistence for that session. -
--budget Nflag for the l10n parity gate.check_reference_parity.py --budget Nfails the gate only when baseline entries exceed N, enabling "fix N entries per sprint" CI enforcement without the all-or-nothing of--strict. Guarded against conflicting flag combos (--budget+--strict,--budget+--no-baseline). -
Annotate endpoint now rejects invalid JSON with 400.
POST /api/session/{id}/annotatepreviously fell back to an empty map when the request body could not be parsed as JSON, silently creating an annotation with no text or author. It now returns 400 with a structured error usingServerConstants.jsonKeyError. -
Issue reporting guide. Replaced
bugs/BUG_REPORT_GUIDE.mdwith a broaderISSUE_REPORT_GUIDE.mdcovering bugs, feature requests, and proposals — aligned withsaropa_lints' structure. Added file naming conventions, attribution evidence requirements, investigation checklist, common pitfalls, fix requirements, lifecycle diagrams, and severity guide. Updated GitHub issue templates (bug_report.yml,feature_request.yml) with severity dropdown, emitter attribution field, and detection/behavior section, and added.github/ISSUE_TEMPLATE/config.ymlto route all new issues through the structured templates (blank_issues_enabled: false). -
Archived 7 fixed bugs. Moved closed bug files (012, 013, 036, 037, 063, 075, 076) from
bugs/toplans/history/2026.09/20260903/per the project archival convention. Repointed cross-references in bugs 065 and 073. -
Dangling bug-reference gate. New
scripts/check_bug_ref_dangling.pyscans staged files forbugs/<file>.mdpaths where the target no longer exists inbugs/— catches stale references left behind after archiving. Wired into pre-commit as Gate 8. Files insideplans/history/are excluded (historical context, not live references). -
Publish pipeline: retry/skip/abort on Dart analysis, downgrade, outdated, docs, and dry-run steps. These five steps previously hard-aborted the entire pipeline on failure. They now offer the same retry/skip/abort prompt the test and lint steps already have, so a transient or externally-caused failure (e.g. upstream plugin emitting unsupported-option warnings) no longer forces a full pipeline restart.
-
Fixed 2 stale server-origin storage tests.
clearStaleProjectStoragewas refactored to usesafeSetItem/safeGetItemwrappers (fromstorage.ts) instead of rawlocalStoragecalls with inline try/catch, but the tests still asserted the old pattern. Updated to verify the safe-wrapper delegation.
4.2.5 #
Error messages now tell you whether the problem was your SQL or a bug in the server itself. log
Added #
- Error classification for the
onErrorcallback. NewDriftDebugErrorKindenum (userQuery,server) andDriftDebugOnClassifiedErrorcallback let host apps distinguish expected user-input errors (bad SQL, unknown column) from genuine server bugs (bind failure, snapshot corruption). PassonClassifiedError:tostartDriftViewerorDriftDebugServer.start; when set, it fires instead ofonErrorwith the error kind attached.DriftDebugErrorLogger.classifiedErrorCallback()provides a ready-made implementation that logs user-query errors at info level and server errors at SEVERE. ExistingonErrorcallers are unchanged.
Internal #
- Localization translate-gaps pass no longer cycles without progress. Forced-identity keys (brands, acronyms, symbol-only strings) that were missing from locale bundles were never written because the translate action skipped them, so
missing=25persisted across runs. They are now written with their English value during the translate pass. AddedNULL,PNG,SVGto the acronym list; enhanced the no-translatable-content detector to strip known acronyms (word-boundary-aware) before checking for ASCII letters (resolves strings like✓ FK {0} → {1}); added per-locale verified cognates (Schemain Italian,Status/Total/Regexin Portuguese,msin Korean). Theis_verified_identicalcheck now strips placeholders before matching, so a single cognate entry (e.g.,Total) covers all placeholder variants (e.g.,Total: {0}). - Auto-cognate detector. After a translate pass, any key where MT returned the English text unchanged is written to a
*_cognate_candidates.jsonreport. Confirmed entries can be added toVERIFIED_IDENTICALinbrands.pyto prevent future cycling. - Hardened
doc/API.mdversion sync.sync_api_md_versionnow replaces only the current header version (old→new), not any semver-shaped text. The@vX.Y.Zpattern is anchored tocdn.jsdelivr.netURLs. Dry-run mode added (dry_run=Truereturns a change report without writing). 20-test Python suite (test_target_config_api_md.py) covers replacement, preservation of example payloads/IPs/prose semver, round-trip, dry-run, andensure_api_md_version_syncguard rails.
4.2.4 #
Exported reports no longer count the schema browser's own lookups as app queries, and each export now records which extension and server version produced it. log
Fixed #
-
Performance analytics no longer counts schema-browser introspection as application queries. Opening the schema browser issues one
PRAGMA table_infoper table; these were filling the "recent queries" list (and could evict real app queries from the timing buffer), so an exported report showed the advisor measuring itself instead of the app. PRAGMA statements are now excluded from query totals, slow queries, patterns, and the recent-queries list. Seeplans/history/2026.07/2026.07.24/BUG_EXPORT_PERF_SECTION_FALSE_POSITIVES.md. -
Anomaly outlier checks can be silenced on static/seed tables.
startDriftViewer(andDriftDebugServer.start) accept a newstaticTables:list; the max-vs-mean outlier scan is skipped for those tables, since an outlier in immutable seed data can never indicate a defect. Other checks (missing values, orphaned references) still run on them. When an outlier is found on a table you have not marked static, the finding now carries a one-line hint naming the table and the exactstaticTables:snippet, so the fix is discoverable from the finding itself. The hint also auto-suggests which tables are the likely static candidates — outlier tables with no writes or data changes observed during the session — and flags the ones that did change as probably not static, so you can copy the snippet with more confidence.
Added #
- App query timings now reach the performance report. New
DriftDebugServer.reportAppQuery(...)lets your app forward its own Drift query timings to the advisor, soperformance.totalQueries, slow queries, and the exported report finally reflect real application traffic instead of only advisor-issued queries (which is why exports previously showedtotalQueries: 0). Wire it with a DriftQueryInterceptor— a complete, tested one ships in the example (example/lib/database/advisor_timing_interceptor.dart), installed withexecutor.interceptWith(...). Full integration guide: doc/APP_QUERY_TIMING.md. Reported queries are taggedsource: "app", and writes feed the static-table candidate ranking so those suggestions become reliable. When no app query has been captured, the performance report carries a one-linehinttelling you to install the interceptor — so the fix is discoverable right where the empty stats show up. - Exported reports are now version-stamped. The
.drift-advisor.jsonsidecar carries aversionsblock with the extension version and the connected server version, so a report can be tied to the release that produced it. GET /api/docsserves the full API reference as Markdown. A non-UI client (AI coding agent, CLI script) can now read the REST contract from the running server without internet access or a GitHub checkout. Served from the package root on disk; available even while the monitoring kill switch is engaged.
Internal #
HealthResponsetype gains an optionalversionfield (already emitted by/api/health).DriftAdvisorSidecargains an optionalversionsblock. Regression test added intest/performance_handler_test.dartfor PRAGMA exclusion.staticTablesthreaded throughstart()/_startInternal/stub/startDriftViewer→ServerContext.staticTables→AnomalyDetector.getAnomaliesResult(auto-derivespotential_outliersuppressions) and the twoanalytics_handlercall sites. Newoutlier_check_hintanomaly type (additive to the issues envelope). Regression tests intest/anomaly_detector_test.dart.- Parallelized independent sequential awaits in
analytics_handler(PRAGMA queries, per-table stats),compare_handler(schema/table queries, column maps), andreport_handler(table info/rows/count) — uses typed record.waitto avoid unsafeascasts. Converted manual index loops toasMap().entriesinedits_batch_handler,index_batch_handler, andsql_validator. Extracted_dispatchRouteshelper inRouter— route dispatch is now a documented single-point closure loop, preventing new routes from being added outside the dispatch chain. Added long-poll delay comment ingeneration_handler. Suppressed false-positive lint warnings with inline rationale where sequential execution is intentional (shutdown ordering, guard-then-query, in-place mutation). Fixedexample/pubspec.yamlformatting and dependency ordering. - Feature 61 hardening: the introspection filter now matches
sqlite_master/sqlite_schemaonly as aFROM/JOINtarget (a literal mention in an app query still counts); the no-app-queries hint leads withreportAppQueryso it fits callback-API users, not only interceptor users;reportAppQuerydocuments its same-isolate requirement; the exampleAdvisorTimingInterceptorgainedrunCustom/runBatchedoverrides and is installed only inkDebugMode(zero release overhead).DriftDebugServer.reportAppQuerygained an end-to-end test; new tests for the literal-sqlite_mastercase and the interceptor's custom/batched paths. doc/API.mdupdated to version 4.2.4: added 16 previously undocumented endpoint sections (views, declared schema, relationships, report, snapshots list/delete/rename, cell update, index preview/apply, soft relationships, query history, DVR, mutations,/api/docs); added HTTP 403 status code; expanded query parameter reference table. DVR 404 response now documents the extraerror/messagesibling keys alongside the envelope.GET /api/docsendpoint: new route in_routePreQuery(pre-gate, no DB dependency) servingdoc/API.mdvia the existing_sendWebAssetfile-serving pipeline. Path constantspathApiDocs/pathApiDocsAltadded; endpoint listed inhealthEndpointsandapiIndexEndpoints.test/version_sync_test.dartgains a test that assertsdoc/API.md's**API version:**header matchesServerConstants.packageVersion, preventing the version string from drifting on future releases.- Publish pipeline now auto-syncs
doc/API.mdversion strings from pubspec.ensure_api_md_version_syncruns pre- and post-bump (mirroring the server-constants sync) andwrite_version(DART, ...)callssync_api_md_versionso--bumpflows also update the doc. Exit-code mapping added for the newAPI doc versionstep.
4.2.3 #
New schema diagnostics warn about missing schema snapshots and catch version mismatches before they cause silent black screens. log
Added #
-
no-schema-snapshotsdiagnostic (Warning). Warns when a Drift project has nodrift_schemas/ortest/generated_migrations/directory — prompts developers to rundart run drift_dev schema dumpand set upSchemaVerifierfor migration path testing. -
schema-version-mismatchdiagnostic (Error). Reports when the database'sPRAGMA user_versiondiffers from the Dart-declaredschemaVersion, indicating a migration that failed or was skipped — the exact scenario that causes silent black screens on older installs. -
declaredSchemaVersionthreading. The Dart server now derives the host database's declaredschemaVersionvia duck typing and exposes it alongsidedbSchemaVersion(fromPRAGMA user_version) in the/api/schema/metadataresponse and VM Service bridge. -
AnomalySuppression.copyWith. Returns a copy with selectively replaced fields. Nullable fields (column,type) use a factory-function parameter to distinguish "not specified" from "explicitly set to null". -
"Validate Migration Paths" command. Scans
drift_schemas/for version snapshot files, reports any gaps between the lowest and highest version number (e.g. v1 and v3 present but v2 missing), so developers know whichdrift_dev schema dumpruns are needed before SchemaVerifier can cover every upgrade path.
Fixed #
no-schema-snapshotsnow uses monorepo-safe glob patterns.findFilescalls use**/drift_schemas/**and**/test/generated_migrations/**instead of root-anchored globs, so schema snapshot directories inside sub-packages are correctly detected.- Lint compliance for
_deriveDeclaredSchemaVersionignore directive. Added rationale comments satisfying bothdocument_analyzer_ignore_rationaleandprefer_commenting_analyzer_ignoresrules. - SchemaVerifier codegen input hardening. Import path prompt now rejects a
package:prefix (the template prepends it automatically), preventing a double-prefix in the generated import. Info message clarifies that the file must be saved undertest/for the relativegenerated_migrationsimport to resolve.
Internal #
- Simplified
AnomalySuppression.matchesguard. Replaced trailing if-return-false / return-true with a single boolean return to satisfyavoid_unnecessary_iflint. Added explicitString?cast on theanomaly['type']lookup for type-safe comparison, matching the existing pattern fortableandcolumn. - "Generate SchemaVerifier Test" code action. The
no-schema-snapshotsdiagnostic now offers a quick fix that scaffolds a DriftSchemaVerifiertest file — prompts for the import path, then opens an editor with the generated test code. - Test mock isolation.
afterEachin schema-provider and best-practice-provider tests now resetsworkspace.workspaceFoldersto prevent state leaking between test files. - Dart-side
getDbSchemaVersionanddeclaredSchemaVersiontest coverage. Newtest/schema_version_test.dartcovers PRAGMA user_version parsing (int, non-int, empty, error),declaredSchemaVersionthreading throughServerContext, andnormalizeRowskey-casing behavior. createTestContextacceptsqueryRecorder. Tests that construct aRoutercan now supply aQueryRecorderwithout building a full server.- Web stub parity. Added
declaredSchemaVersionparameter toDriftDebugServer.startstub so web builds compile. - Publish pipeline: retry prompts on failures. Remote sync, dependency, and Dependabot PR steps now ask retry/ignore/cancel instead of aborting immediately.
4.2.2 #
Servers can now suppress specific anomalies before they hit the response, and outlier detection stops crying wolf on size, duration, and measurement columns. log
Added #
- Server-side anomaly suppression.
AnomalyDetector.getAnomaliesResultaccepts asuppressionsparameter — a list ofAnomalySuppressionrules that filter anomalies by table, column, and/or type before they reach the JSON response or server logs. This is the server-side equivalent of the extension's// drift-advisor:ignoreinline directives: callers collect suppressions from any source (parsed Dart comments, host configuration, user settings) and pass them to the detector. Wildcard table (*) and null column/type (match all) are supported.
Improved #
- Publish pipeline offers to merge Dependabot PRs inline. The Dependabot gate now lists open PRs and prompts to squash-merge them via
gh pr mergewithout leaving the terminal. On success it pulls the merged commits into the local branch automatically. Declining the merge falls back to the previous continue-anyway prompt.
Fixed #
- Outlier false positives on dimensional and physical measurement columns. The anomaly detector now skips columns whose names indicate byte sizes, pixel dimensions, durations, counts, bandwidth, throughput, and latency (
byte_size,width,height,file_size,content_length,pixel_*,num_*,*_count,duration,area,volume,depth,bandwidth,throughput,latency), and physical measurements (weight_*,mass,distance,speed,velocity,temperature,pressure,capacity). Previously these triggered spurious INFO-levelpotential_outlierfindings because their naturally wide or bimodal distributions are not Gaussian. - Bare
countcolumn no longer suppressed. Removed the over-broad^count$match from the dimension skip pattern — a column named exactlycountis ambiguous and could be a meaningful metric. Suffixed forms likeitem_countand prefixed forms likenum_itemsremain skipped.
4.2.1 #
A new Heartbeat screen shows your database's pulse live — tables glow as they are read and written — and other Saropa Suite tools can now pull a small daily digest from Drift Advisor for a consolidated Suite report. log
Added #
- Heartbeat / Watch screen (Feature 80, phase 1). A live activity board in the web viewer (and the VS Code webview, which shares the same UI): every table with recorded activity appears as a card whose border glows on access — cool for reads, warm for writes — with per-table read/write counters, and the glow decays in under two seconds so rapid traffic burns brighter. An ECG-style monitor strip across the top aggregates all activity into a scrolling heart-rate trace with a live events-per-minute vital; idle databases show a calm flatline sweep. Untouched tables are never listed. Host-app writes are detected indirectly from row-count changes and labeled "Detected changes" (UPDATE-in-place is invisible in phase 1); the app's own reads are not visible — see
plans/history/2026.07/2026.07.16/80-heartbeat-phase2-host-capture.mdfor the phase 2 capture design. Backed by a new in-memoryTableActivityTrackerandGET /api/activityendpoint (documented indoc/API.md): aggregates plus a bounded 200-event ring, internal/self-issued queries excluded so the board never glows from watching itself, and fully disabled under the monitoring kill switch. - Heartbeat screen: per-table sparklines and idle-friendly polling. Every table card now carries its own miniature 30-second activity trace in the same monitor style as the main ECG strip (warm-tinted when the interval contains writes; static bars under reduced-motion). Polling adapts to traffic: ~750 ms while events arrive, decaying stepwise to a 2.5 s ceiling after eight quiet polls and snapping back instantly on activity — an idle screen no longer issues 80 requests a minute against a battery-powered debug target. Theme switches recolor the monitor and sparklines on the next frame. Table attribution is now CTE-aware: a
WITHalias is never mistaken for a table, real tables inside CTE bodies still register, and schema-qualified names likemain.itemsattribute the table instead of the schema. - Heartbeat screen: capture the app's own live traffic (Feature 80, phase 2). Wire one line in the host app —
DriftDebugServer.reportActivity(sql)from drift'slogStatementsor aQueryInterceptor— and a new "Capture live app traffic" toggle on the Heartbeat screen lights the board with the app's OWN reads and writes, not just advisor traffic and detected row-count changes. Safety is structural: capture always starts off, only the heartbeat screen can arm it, and arming grants a ~5-second lease that the screen's own polling renews — so a closed tab, dropped adb forward, or crashed webview can never leave the per-query hook hot (the screen also disarms on tab switch, hidden visibility, and page unload). While off, the hook costs the host one boolean check per query; a pulsing "Capturing" badge makes the armed state unmissable, and disabling server monitoring force-disarms it. NewPOST /api/activity/captureendpoint and acaptureArmedfield onGET /api/activity, both documented indoc/API.md. - Heartbeat screen: statement tap — a live query inspector on every card. A new receipt icon on each table card opens a flyout listing the last 10 statements the app ran against that table (newest first, read/write channel dot, relative age), fed by a new
GET /api/activity/statements?table=Xendpoint over bounded per-table rings the server fills while capture is armed. The flyout explains itself when empty: with capture off it says to turn on "Capture live app traffic" rather than showing a bare nothing. Capture hardening in the same change: leading SQL comments (-- tag,/* trace */) no longer defeat statement classification; a ~200/second cap drops burst overflow before any parsing so an armed capture can never soak the host app's CPU; the screen holds its steady 750 ms poll while capture is armed (an armed capture is active observation, never "idle") instead of decaying toward the lease's edge; andreportActivity's docs now state the raw-SQL and same-isolate requirements explicitly. - Saropa Suite daily-summary API. The extension's exports now implement the cross-tool Suite contract (
apiVersion: 1,getDailySummary(date)) alongside the existing Log Capture snapshot. A sibling extension callsgetExtension('saropa.drift-viewer')?.exports.getDailySummary('YYYY-MM-DD')and gets a one-sentence headline, named counts (queries, slow queries, anomalies, index suggestions), a failure-only Trouble list with deep-links, and an open-command — orundefinedwhen no database is connected. It is a thin read-only projection of already-computed session data, built lazily on call so activation is unaffected; per-day history is not retained, so apiVersion 1 returns the live session view stamped with the requested date. Documented indoc/EXTENSION_API.md(plans/history/2026.07/2026.07.16/PLAN_suite_daily_summary_api.md). - Ignore a Drift Advisor finding from a right-click, not just the lightbulb. The Problems panel's row context menu is a fixed VS Code menu extensions cannot add to, so double-clicking a finding there only moved the cursor near it without exposing a way to suppress it. Right-clicking anywhere within 3 lines of a finding in the editor now shows "Ignore Finding for This Column" / "Ignore Finding in This File" (also in the Command Palette), which resolve the intended finding from the cursor's line — falling back to the nearest finding within that same window, and prompting to choose when several tie for closest (same line or not) — before inserting the same
// drift-advisor:ignoredirective the lightbulb quick fix writes. A finding with no diagnostic code is refused rather than silently written as a suppress-everything directive.
Fixed #
- Dashboard and Watch panels no longer lose their first data update after opening. A race between the extension pushing data and the webview registering its listener silently dropped the first message; panels now queue messages until the webview script signals ready.
- The extension no longer hammers an unreachable server indefinitely on Windows. The circuit breaker's dominant failure path on Windows (a safety timeout) was not counted as a transient error, so the breaker never tripped. All non-abort errors now count toward the breaker threshold. A half-open guard also prevents concurrent probes from racing the breaker state.
- A failing endpoint no longer blocks unrelated features. A single global circuit breaker meant one bad endpoint (e.g. analytics) could trip the breaker for schema, DVR, and everything else. Each endpoint group now has its own independent breaker; if the server is genuinely down, all groups trip within seconds. User-initiated Retry Discovery resets all groups.
- Right-click "Ignore Finding" no longer risks inserting a directive for a stale finding. The QuickPick tie-break (when several findings are equidistant from the cursor) now re-validates the picked finding against fresh diagnostics before inserting, so a directive can no longer land for a finding that changed or vanished while the pick was pending. The
onDidChangeDiagnosticslistener backing the right-click menu now filters events to the active editor's document instead of recomputing on every extension's diagnostics change.
Improved #
- The extension detects a newly started debug server within seconds instead of up to 60 s. The Dart server now posts a VM Service Extension event on startup; the extension listens for it and triggers an immediate discovery scan, closing the "server running but extension doesn't know yet" window. Polling remains as the fallback.
- Port forwarding to a device is automatically re-established when it drops. During a Dart/Flutter debug session, the extension watches
adb forward --listevery 15 s and re-creates the mapping when it silently dies (device reconnect, adb server restart, editor crash mid-debug). Previously a dead forward was only healed reactively — up to scan-interval + 60 s throttle of unexplained "server lost." Only acts on a confirmed drop (the forward must have been observed alive this session first), honors the 60 s re-forward throttle, and preserves the once-per-session toast latch on recovery. - After repeated connection failures, the extension short-circuits requests for 30 s instead of hammering the network. A circuit breaker gates all outbound HTTP — after 5 consecutive transient failures, requests are rejected immediately instead of every subsystem independently retrying. Discovery health probes bypass the breaker (they are the recovery mechanism). User-initiated retry resets the breaker.
Internal #
- NLLB no longer loads when Qwen is available. The engine cascade was eagerly constructing
NllbTranslator(loading the 3.3B model into GPU memory) even when Qwen was the active engine, wasting ~2 GB VRAM and adding startup delay. NLLB now only loads when Qwen is unavailable. - Brand-token placeholders survive Qwen translation. Brand-shield tokens (
<B0>,<B1>, …) were sent raw to the Qwen model, which dropped or mangled them.validate_brandsthen rejected every translation containing a brand name ("wrote 0 translations"). The placeholders are now masked alongside format placeholders ({count},{name}) before the model sees the text, then restored after. - Translation engine: NLLB → Qwen 2.5 7B (via Ollama). The primary offline translation engine is now Qwen 2.5 7B running locally through Ollama's OpenAI-compatible API, replacing NLLB-200 3.3B. NLLB remains as a fallback when Ollama is not running; Google Translate is the last resort. Qwen produces materially better translations for UI strings. Existing NLLB-provenance translations are now classified as medium quality and eligible for upgrade via the "Upgrade LOW/NLLB-QUALITY → Qwen" menu action.
- Qwen prerequisite diagnostics. The interactive menu now checks Ollama status at startup and shows actionable fix instructions when something is missing — env-disabled, server not running, or model not pulled — instead of silently falling back to a weaker engine.
- Connection telemetry. Every real connection phase transition (disconnected/connecting/connected/offline) is now logged to the Output channel with time-since-activation, a running flap count, and the measured reconnect latency when the connection comes back. Log-only — it changes no connection behavior, and it is the measurement instrument any future threshold tuning is gated on.
- Connection reliability implementation (
plans/connection-reliability-ongoing.md): circuit breaker (CircuitBreaker→CircuitBreakerRegistry), webview ready-handshake (postMessagequeueing), server push discovery (ext.saropa.drift.ServerStartedVM Service event), and adb-forward supervision — see the Fixed and Improved sections above for user-facing descriptions. - Publish pipeline: Dependabot PR gate. The pipeline now checks for open Dependabot PRs after fetching origin — blocks publish (with override prompt) if stale dependency PRs are waiting, so releases never ship on deps that Dependabot already flagged.
- Audit closure: C2b phase 2 (nonce CSP for the browser-served SPA + data-grid webview) closed WONTFIX — defense-in-depth only on surfaces already protected by loopback default + fixed XSS sinks. The full codebase audit has no remaining open items. Deferred plan archived to
plans/history/. - 96 brand-mangled translations hand-written and patched. The MT engine (Qwen) dropped keys across 10 locales (de, es, fr, it, ja, ko, pt-br, ru, zh-cn, zh-tw) because it altered brand names (Drift, Saropa, Isar, Flutter, SQLite, WAL, VM Service). All 96 were manually translated with brand terms, HTML tags, and
{0}/{1}placeholders preserved, then inserted intobundle.l10n.*.jsonandassets/web/l10n/web.*.json. - Full codebase audit archived. With its last open item closed, the audit document moved from
plans/toplans/history/2026.06/2026.06.12/full-codebase-audit-2026.06.12.md; the ~30 source comments and docs citing the old path were rewritten to the archive path, and a pointer stub remains atplans/full-codebase-audit-2026.06.12.mdso stale external references still resolve.
4.2.0 #
One switch now turns ALL monitoring off: a power button in the Database sidebar (plus a card in Drift Tools and two commands) instantly stops query recording, background sweeps, diagnostics, and file badges on both the extension and the in-app debug server — and turns them all back on without any restart. Booleans now render as true/false instead of 0/1, interactive SQL errors now suggest the right column name, and the web viewer's left icon bar is a touch roomier with softly tinted icons. log
Added #
- Global monitoring & logging kill switch across the whole toolchain. For performance-sensitive debugging, privacy compliance, or constrained devices, one control now silences everything at once (
plans/PLAN_BUILD a KILL SWITCH.md):- VS Code: a new
driftViewer.enableMonitoringAndLoggingsetting (default on), a power toggle in the Database sidebar toolbar, a status card at the top of the Drift Tools Hub ("Monitoring Active" / "Monitoring Suppressed" with a one-click Kill/Resume button), and two Command Palette commands —Drift Viewer: Kill All Monitoring and Logging/Resume All Monitoring and Logging. Killing clears all Problems-panel diagnostics and row-count file badges immediately, blanks the Database sidebar with a "Monitoring and Logging are disabled via Kill Switch." notice (with a one-tap resume row), and stops the heavy background sweeps. Resuming re-arms everything without a window reload. - Dart server:
DriftDebugServer.start(monitoringEnabled: false)boots the server dormant, andDriftDebugServer.setMonitoringEnabled()or the newGET/POST /api/monitoringendpoint flip it live. While killed, the server records no query timings, no DVR entries, and runs no change-detection sweeps; every data-inspection endpoint answers a structured403("Access Denied: All monitoring and data inspection has been halted by the global kill switch.") while/api/healthkeeps responding withmonitoringEnabled: falseand the discovery manifest advertises"monitoring": "disabled"so external tools can tell "deliberately dormant" from "broken". - The extension pushes the kill state to any server it connects to (and warns, with a one-tap resume, when a connected server is itself dormant), and API errors caused by the kill switch surface the explanatory message instead of a bare
403. - The switch covers BOTH transports: the Dart VM Service RPCs (
ext.saropa.drift.*, including SQL and batch edits) refuse with the same message while killed, and newgetMonitoring/setMonitoringRPCs let a VM-only debug session flip the switch when no HTTP port is reachable.GET /api/mutationsis also gated so row data captured before a kill cannot be read while killed.
- VS Code: a new
/api/sqlerrors now carry schema-aware hints instead of a bareSqliteException. Ano such columnfailure previously returned only SQLite's terse text, so a client had to already know the exact Drift-generated name — including acronym splitting (contactSaropaUUID→contact_saropa_u_u_i_d) and reserved-word rules — with zero assistance from a tool whose whole purpose is schema awareness. The Advisor now enriches these errors after SQLite rejects the statement (so there are no false positives): it resolves the referenced table from the query'sFROM/JOINclauses, appends that table's actual column names, and — when the mistake is a plausible typo — suggests the nearest real column, matching the guidance the source-file column checker already gives for Dart raw SQL. A reserved SQLite keyword used as a bare alias (... AS primary) now returns a hint to quote or rename it (plans/history/2026.07/2026.07.04/BUG_API_SQL_UNVALIDATED_COLUMN_REFS.md).
Fixed #
- Boolean columns now display as
true/falseinstead of0/1whenever the connected app declares its Drift schema. SQLite stores Drift booleans asINTEGER, and the viewer previously guessed booleans from column names alone (is_*,has_*, …), so any boolean with a non-matching name rendered as a bare integer. The data grid, the search tab, the inline cell editor, and custom SQL results now read thedriftTypethe backend already sends (exact, no guessing); the VS Code sidebar shows a boolean icon and abool (INTEGER)label for these columns. Custom SQL results only format a column when its name is a bool in every table that declares it — an ambiguous name stays raw. Raw SQLite hosts and older servers keep today's name-heuristic behavior (plans/history/2026.07/2026.07.09/BUG_bools_showing_as_ints.md). - Boolean name detection now matches suffix-named columns (
user_active,account_enabled, …). The suffix pattern used a Dart-style escaped\$, which in a JavaScript regex matches a literal dollar sign rather than end-of-string, so the entire suffix branch never matched any real column name. Date name detection (expires_at,starts_on) carried the same\$artifact and is fixed the same way. - The grid and the inline cell editor now agree on which columns are booleans. They previously used different integer-type lists, so a
user_active INTcolumn validated its edits as a boolean while still displaying as0/1; both now share one predicate. Query-builder results from raw SQL or multi-table joins no longer borrow the current table's declared types for same-named result columns — like custom SQL results, they format only names that are bool in every declaring table. A deep-linked?sql=run now loads schema metadata before rendering its first result, so booleans format correctly even when the SQL tab is the first surface opened. - Multi-line
SELECT/WITHqueries are no longer rejected as non-read-only. The read-only check required a literal space right after the leading verb, so a pretty-printed query with a newline afterSELECT(the default editor formatting, e.g.SELECT\n id, ...) failed with "Only read-only SQL is allowed (SELECT or WITH ... SELECT)." The check now accepts any whitespace — space, tab, or newline — after the verb (bugs/BUG_showing_false-read-only-error.md).
Improved #
- Web viewer activity bar widened ~20% with lightly tinted icons. The vertical icon strip (Home, Tables, Search, and the tool launchers) now uses larger 2.4rem buttons and slightly more side padding, giving the 20+ icons more breathing room and bigger tap targets. The resting icons are tinted with a soft, theme-aware blend of the accent and muted colors instead of flat gray, so the strip reads as interactive and scans faster; hover and active states still escalate to the full foreground/accent color. Scoped to the activity bar, so the tab-bar icons are unchanged.
Internal #
- Split five over-cap extension source/test files into focused modules to satisfy the 300-line (source) and 500-line (test) caps: the Phase-10 event wiring extracted its auto-capture recommender and heavy-sweep scheduler; the discovery core extracted its scan-result/state-machine updater and UI-snapshot builder; the tree provider extracted its refresh orchestrator; the vscode test mock split its clipboard/dialog/message/fs backing stores into separate files; and the snapshot-store test split its
rowsToObjects/computeTableDiffblocks and shared helpers into their own files. Behavior is unchanged. A review pass caught and corrected four behavior-parity breaks introduced by the extraction before they shipped: the discovery change event was firing a one-generation-stale server list (would have blocked first-scan auto-connect), the tree refresh cleared the table list on a safety-timeout abort (should preserve the last-known/offline schema), the coalesced pending refresh bypassed the monitoring kill switch, and the tree refresh captured the pin store at construction (beforesetPinStoreruns, so pins never rendered). Added a discovery regression test asserting the change event's payload — not just theserversgetter — carries the freshly-found server.
4.1.17 #
The snapshot, branch, and data-breakpoint sweeps no longer pull raw image/attachment BLOB bytes, so they can't crash a connected app that stores them — and timeline auto-capture now ships off by default, with a one-time prompt offering to turn it on. log
Fixed #
- Capture sweeps no longer crash a connected app that stores image/attachment BLOBs. The timeline snapshot, data branch, and data-breakpoint "row changed" sweeps issued
SELECT *over every table; on a table holding avatar/photo/attachment BLOBs under the row-count cap, that pulled up to a thousand multi-KB–multi-MB blob rows into the connected app's isolate to serialize the response, exhausting native memory and aborting the process (plans/history/2026.06/2026.06.28/BUG_TIMELINE_CAPTURE_SELECT_STAR_BLOB_OOM.md). These sweeps now read alength()of each BLOB column instead of its bytes — enough to detect a row changed without ever transferring the payload — so the connected app stays alive regardless of how large its blobs are. A blob edited to a different value of the same byte length is the one change this won't flag.
Changed #
driftViewer.timeline.autoCapturenow defaults to off. Auto-capture re-dumps every physical table on each data change; shipping it off makes that automatic re-dump opt-in rather than a surprise. It is safe on any schema — including BLOB-bearing ones — because the capture reads each blob's length, not its bytes (see the crash fix above). Snapshots are still available any time via the Capture Snapshot command. The setting description and README document the behavior.
Added #
- A one-time prompt offers to enable auto-capture. On connect, if auto-capture is off and the connected database has a readable schema, a prompt (shown at most once per workspace) offers to turn it on for that workspace. It is not gated on schema shape or size — auto-capture is safe everywhere now that the sweep never transfers blob bytes.
4.1.16 #
Row-count file badges now render on every Drift table file — including large tables — and no longer spam the extension-host log. log
Fixed #
- Row-count file badges now show on tables of every size and stop flooding the extension-host log. The badge label could exceed VS Code's two-character limit for whole row-count bands (100–999 rows, and roughly 9 500 rows and up — e.g.
"100","10K","999K","10M"). VS Code rejects an over-length badge: it dropped the decoration entirely (so exactly the large tables that most need a count showed none) and logged anINVALID decoration … 'badge'-property must be undefined or a short characterwarning once per offending file on every badge refresh — hundreds of lines per refresh, compounding on a reconnecting link. The badge is now always two characters or fewer: exact counts under 100, then a leading digit plus a magnitude letter (3H,5K,2M,1B) or the bare letter when even that won't fit, with the full per-table counts still in the hover tooltip.
Internal #
formatBadgerewritten to be total-safe to ≤2 characters (Math.floorinstead ofMath.roundso values like 9 500 stay"9K"rather than overflowing to the 3-char"10K"; guards non-finite and non-positive input). Added a defensive guard at theFileDecorationcall site that omits the badge (keeping the tooltip) if a label ever exceeds two characters, so a future regression cannot reach VS Code. Added a unit test assertingformatBadge(n).length <= 2across the full range plus updated the band-specific expectations. Fixesplans/history/2026.06/2026.06.27/BUG_file_decoration_badge_exceeds_two_chars_floods_exthost_log.md.
4.1.15 #
The "Drift debug server detected" toast no longer keeps re-popping on a flaky wireless-debugging connection. log
Fixed #
- No more repeated "Drift debug server detected on port 8642" toasts on a flapping wireless link. When the Drift server runs inside the app on a device reached over Android Wireless Debugging, a dropped-and-recovered link triggered an automatic
adb forwardrecovery that restarted discovery and re-armed the once-per-session toast latch — so every reconnect (roughly every 1–few minutes on a flaky link) re-showed the "detected" toast with its action buttons. The automatic recovery path now preserves the latch, so the link flap stays silent after the first detection (and the single "no longer responding" warning). A user-initiated "Retry Discovery" still re-announces as before.
4.1.14 #
The "Code vs database" schema view no longer reports false drift for DateTime columns or autoincrement id columns. log
Added #
- Search box in the History sidebar. A filter field above the history list narrows entries to those whose SQL contains your text (case-insensitive), working alongside the existing All / Browser / App / Internal source filters. Typing filters instantly, and a clear "no queries match" message shows when nothing matches.
Fixed #
- DateTime columns no longer show a false
code TEXT vs database INTEGERdivergence. The code-declared schema hard-mapped every DriftDateTimecolumn to TEXT, but Drift's default storage is INTEGER (unix-epoch seconds) — TEXT only when the database setsstoreDateTimeAsText. The declared schema now reads that option and maps DateTime to the affinity the live database actually uses, so default-storage apps (the common case) report no drift. - Autoincrement
idcolumns no longer show a falsecode not null vs database nullabledivergence. A single-columnINTEGER PRIMARY KEYis a SQLite rowid alias, and SQLite always reports it as nullable inPRAGMA table_infoeven though it cannot hold NULL. The divergence check now skips the nullability comparison for these rowid-alias primary keys, while still flagging real nullability drift on ordinary columns and on composite or non-integer keys. - The theme menu no longer gets cropped by the left activity bar. The theme flyout is now anchored over the page instead of inside the icon strip (which clips its content), and it stays fully on-screen — so every theme option is visible when you open it.
Improved #
- Home screen polish. The feature launcher grid now has breathing room below the last row, and the feature-search box indents its text and placeholder clear of the search icon.
- Wider left activity bar. The icon strip on the left is a touch wider so its buttons and labels sit more comfortably.
4.1.13 #
The timeline auto-capture no longer freezes your app's launch when the extension is connected in debug. log
Fixed #
- Timeline auto-capture no longer stalls host-app startup. On connect, the timeline snapshot sweep read every table with a full
SELECT *in one back-to-back burst over the app's single live database connection. On a host that runs Drift on its main isolate (the standard debug setup), that burst monopolized the connection and froze the app's launch for several seconds. Two fixes: very large tables (over 50,000 rows) are now captured metadata-only — the sweep already truncated them to a misleading partial slice, so it skips the expensive read and still records the row-count change; and a short pause between table reads lets the app's own startup queries run in between, so a capture spreads out instead of blocking the launch.
Historical Changelog Archive #
For older versions see CHANGELOG_ARCHIVE.md.
