saropa_drift_advisor 4.2.5
saropa_drift_advisor: ^4.2.5 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.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.
Maintenance
- 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.
Maintenance
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.
Maintenance
- 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.
Maintenance
- 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.
Maintenance
- 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.
Maintenance
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.
4.1.12 #
Rewind a table in Time Travel, then save that moment as a branch you can diff or restore later. log
Added #
- "Create Branch Here" in Time Travel. While scrubbing a table's history in the Time-Travel panel, a new button saves the database state at the current snapshot position as a named data branch — which you can then diff, generate merge/rollback SQL from, or restore, exactly like a branch captured from live state. The button appears only when Data Branching is available. Snapshots cap rows per table, so a branch made from a large historical snapshot is flagged as truncated rather than passed off as complete.
Maintenance
- Fixed flaky rate-limiting integration test.
handler_integration_test.dart's "returns 429 when rate limit exceeded" test fired three sequential HTTP requests and assumed all three landed in the same one-second window; on a slow CI runner the third request crossed into the next wall-clock window, where the fixed-window counter reset to 1 and returned 200, failing the assertion. The test now fires a burst of concurrent requests so they cluster densely in one window and asserts at least one is throttled (and at least one succeeds), which holds regardless of where second boundaries fall.
4.1.11 #
Raw SQL strings in your Drift code now get the same column checking as the typed query builder — if a customSelect/customStatement query names a column that does not exist on the table, you see a warning while editing instead of a crash at runtime. log
Added #
- New diagnostic
raw-sql-unknown-column. Validates column references insidecustomSelect(...)/customStatement(...)raw SQL against the live profiled schema and flags any column absent from the referenced table, suggesting the closest real column name. Catches the case where a hardcoded name does not match Drift's generated column (e.g. an acronym gettercontactSaropaUUIDproducescontact_saropa_u_u_i_d, notcontact_saropa_uuid) — a bug invisible to the existing Dart-vs-DB drift checks because it lives in an opaque string. Conservative by design: only single-table queries are checked (JOINs and comma-FROM are skipped), aliases and function names are excluded, and unknown tables are ignored. Default severity Warning; suppress per line/file with// drift-advisor:ignore raw-sql-unknown-column. - Host-side discovery for device-hosted servers. When your app runs on a physical device or emulator, the server's own discovery file (
~/.saropa_drift_advisor/server.json) is written on the device and never appears on your computer, so an external agent orcurlclient could not find it without scanning ports or runningadb forwardby hand. The extension now publishes a host-side manifest with the forwarded, host-reachable port and atransportfield (adb-forwardorloopback) the moment a server becomes reachable, and removes it when the server goes away or the extension shuts down. An agent reads one well-known file and connects. The extension never overwrites a manifest a same-machine (desktop) app wrote for itself.
Fixed #
- Activity bar icon slightly undersized. The database glyph in
media/icon-activitybar.svgspanned 14 of the 24-unit viewBox width (cx=12, rx=7), so VS Code drew it a touch narrow next to the codicons around it. Nudged the cylinder width up (rx=8) to bring it in line with the neighboring sidebar icons.
Maintenance
- Split three over-cap source files to satisfy the 300-line quality gate; no behavior change.
server-discovery-core.ts(346 → 290): extracted the once-per-session "server lost" flap debouncer intoserver-discovery-lost-debounce.ts(ServerLostDebouncer) and the searching/backoff/connected cadence into a pure, independently testableserver-discovery-state-machine.ts(nextDiscoveryState/pollIntervalForState).diagnostics/rules-config-html.ts(317 → 164): moved the inline panel CSS intorules-config-styles.tsand the clientpostMessagescript intorules-config-client.ts, matching the pure-builder pattern of the other*-html.tspanels.diagnostics/checkers/raw-sql-parser.ts(321 → 249): extracted the lexer (literal/comment masking + tokenizer) intoraw-sql-tokenizer.ts, leaving the parser to do table/column resolution only.
- Publish line-limit gate now offers retry / continue / ignore instead of a yes/no. The Step 7 quality check previously asked "Continue anyway? [Y/n]" where No aborted the publish. It now prompts
[R]etry(default — re-scan after trimming files),[C]ontinue(proceed, keep the warning on record), or[I]gnore(proceed, drop the warning). A line-limit overrun is advisory, so there is no abort path; a closed stdin (CI) maps to continue so it cannot loop on retry. - Host discovery manifest writer (
host-discovery-manifest.ts). New extension module:writeHostManifest/removeHostManifestpublish and tear down~/.saropa_drift_advisor/server.jsonon the host. It mirrors the in-app manifest JSON schema (so a reader parses one format) plus two host-only fields — asource: "vscode-extension"ownership stamp andtransport. The writer fetches/api/healthbest-effort to enrich the file but always writes a valid (host, port, transport) manifest even when health is unreachable. The ownership stamp gates both write and remove: the extension never clobbers or deletes a manifest written by an in-app (desktop/emulator-on-host) server. Wired intobootstrapExtension's discovery lifecycle (write on first reachable server, deduped by port; remove when servers go empty and on deactivation). 11 injected-IO unit tests cover the schema, the app-owned guard, the unreachable-health path, and error swallowing. Resolves Finding 1 / Enhancement E1+E3 ofplans/history/2026.06/2026.06.24/BUG_agent_discovery_and_resilience_for_device_hosted_server.md; Finding 2 (SQL resilience: statement timeout, row cap, error-envelope, never-empty body) was already in place.
4.1.10 #
Github CI cleanup tasks. log
Maintenance
- Discovery-manifest cleanup no longer swallows its errors. The best-effort manifest delete in
stop()caught and discarded any failure (satisfyingavoid_swallowing_exceptions/require_catch_logging). The server now captures the context'slogErrorsink on start and routes a cleanup failure through the same channel (dart:developer + the caller'sonError), so a recurring delete failure is diagnosable instead of silent. - Tightened
ServerUtils.jsonEncodeFallbackreturn type fromObject?toObject— it never returns null (a null input encodes to the string"null"), so callers no longer carry a redundant null check (avoid_unnecessary_nullable_return_type). - Publish pre-flight analyze now matches CI exactly, so it can no longer ship one store while the other fails. The Dart analysis step in
scripts/publish.pyused to strip theplugins:block fromanalysis_options.yamland runflutter analyze --fatal-infos, which disabled saropa_lints locally — the exact rules CI enforces withflutter analyze --fatal-warnings. The local gate passed on code CI would reject, the script committed/tagged/pushed, the VS Code extension published, and only then did CI catch the warnings and block the pub.dev publish. The step now runsflutter analyze --fatal-warningswith the plugins block intact, byte-for-byte the CI command, before any commit/tag/push — a lint failure now stops the publish locally instead of after a tag triggers CI.
4.1.9 #
The debug server can now tell tools and AI agents what it offers and where to find it, a runaway query can't knock it offline anymore, and the web viewer's sidebar labels and Run SQL screen got a tidy-up. log
Added #
- The debug server now advertises its own API so external tools and AI agents can find it.
GET /api/healthlists the read endpoints, and a newGET /api/returns a self-describing index (version, flags, each endpoint with a one-line description, and a link to the full reference) — so a non-UI client learns the API from one request instead of having to read the source. On startup the server also writes a small discovery file at~/.saropa_drift_advisor/server.json(host, port, version, flags, workspace) so a tool can find the running server without being told the port; it is removed on shutdown.
Fixed #
- A single bad or slow query can no longer take the debug server offline. Each
POST /api/sql(and/api/sql/explain) now has a 30-second statement timeout: a query that hangs returns a clear error and frees the connection instead of wedging the server so that even the health check stops answering.POST /api/sqlalso always returns valid JSON — either{"rows":[...]}or{"error":"..."}— even when a result holds a value that previously broke encoding and produced an empty response. Very wide results are capped (with atruncatedflag and the true row count) so one query cannot stream an unbounded body.
Changed #
- Activity-bar label mode (web viewer): when the sidebar strip shows text labels, every button is now the same width with its icon and label left-aligned, and the rows have vertical spacing so the labels read as a clean aligned list.
- Run SQL screen (web viewer): redesigned the controls above the editor. The Template, Table and Fields pickers are now a clean aligned card instead of a cramped wrapping toolbar, and the Fields list is a compact fixed-height scroll box rather than the tall narrow column it used to balloon into. Saved-query actions are grouped together with "Show as" pushed to the right. The query box also opens taller by default (about seven lines instead of three) so a typical formatted query fits without scrolling; it is still drag-resizable.
Maintenance
- Publish pipeline: format Dart sources at stage time so the husky pre-commit hook never aborts the release commit. The hook runs
dart format --set-exit-if-changed .whenever.dartfiles are staged; the analysis phase formatted early, but--resumeruns skip analysis and any step (or manual edit) between analysis and commit could re-dirty a file, leaving an unformatted file in the index and failing the commit.git_commit_and_pushnow runsdart format .immediately beforegit add(gated by a newTargetConfig.format_before_stage, Dart-only), so the staged content always matches what the hook checks. - Fixed a flaky discovery-manifest test that passed alone but failed in the full suite. The discovery manifest is written to a single global path (
$home/.saropa_drift_advisor/server.json), and dart'spidis identical across the in-process suite isolates, so the other server-starting test files running concurrently overwrote or deleted this test's manifest between its write and its assertions.DriftDebugServer.startnow accepts an optionaldiscoveryDirectoryoverride (threaded as instance state and reused onstopso write and remove target the same file); the test points each run at its own temp directory, making the manifest lifecycle deterministic and removing the prior "home not resolvable" skip.
4.1.8 #
Internal tooling only — no user-facing change. log
Maintenance
- Publish pipeline now stops on diverged history instead of blind-merging. The pre-flight remote-sync check mislabeled a truly diverged branch (origin's history rewritten, local on old SHAs) as "ahead," and the push step then recovered a non-fast-forward by running
git pull --no-edit(a merge) — tangling two near-duplicate ~240-commit histories into a 25-conflict merge mid-release. The pre-flight now detects divergence explicitly and fails with a rebase hint, and the push recovery usesgit pull --ff-only(which cannot merge), stopping loudly on divergence so reconciliation stays a deliberate manual rebase. - Publish pipeline now catches committed-and-gitignored files before tagging. A file that is both tracked and matched by
.gitignoremakesdart pub publish --dry-runexit 65 — previously only in CI, after the git tag and GitHub release were already created. A newgit ls-files -i -c --exclude-standardguard runs in the local pre-flight (Dart and extension legs) and as a CI step before the dry-run, naming the offending files and thegit rm --cachedfix instead of failing with a cryptic exit code. - Fixed pub.dev "Pass static analysis" deductions for dangling library doc comments. Three server files (
html_content.dart,mutation_handler.dart,mutation_tracker.dart) opened with a top-of-file///doc comment but nolibrary;directive, so pana flagged them as dangling library doc comments and docked static-analysis points. Each now carries alibrary;directive after its header comment. - Enabled
dangling_library_doc_commentsinanalysis_options.yaml. This core Dart lint is scored by pana/pub.dev but was not in the package's base lint set, so localdart analyze(and the publish pipeline's analyze step) passed while pub.dev still deducted points. Enabling it closes that gap — the lint now fires locally anddart fixcan auto-insert thelibrary;directive.
The debug server now tells you how to reach it when you debug on a physical device over Wi-Fi, instead of leaving a silent connection-refused when you try the device's network address. log
Added #
- Drift Tools Hub. A new single-page panel that puts the whole toolbox on one screen. Read-only live previews of your Dashboard and Health Score sit side by side at the top — each with an "Open full screen" button to the full interactive panel, and Health-card actions still drill down from inside the hub. Below them, every tool in the sidebar is indexed in a grouped, collapsible launcher: the same six categories (Getting Started, Schema & Migrations, Health & Quality, Data Management, Visualization, Tools), each with an icon, a tool count, and a one-line note on what it does. Tiles carry semantic icons, and destructive actions (Clear All Tables) get a caution accent. A hero bar adds Rescan and a link to the Saropa website. Open it from the top of the Drift Tools sidebar ("Drift Tools Hub") or the command palette. The two preview panes load concurrently behind one cancellable progress notification; if one fails it shows a placeholder without blanking the other, and the launcher is usable immediately while they load.
Changed #
- The "Drift Tools" sidebar panel is now a slim launcher and moved to the top of the Saropa activity-bar container (above Database). It shows a prominent "Drift Tools Hub" entry (with the extension version), the "Add Saropa Drift Advisor" setup item when the package is missing, and a connection-status row that opens connection help when no server is connected. The previous category-per-tool list was redundant with the new Hub — open the Hub for the full, grouped tool catalog.
- Configure Diagnostic Rules screen. A new full-page panel replaces the old "Drift Advisor Rules" sidebar list. Every rule is grouped by category with its live finding count; each has an enable/disable toggle and a severity dropdown (Default / Error / Warning / Info / Hint). A filter box narrows the list by code or description, and one-click "Enable All" / "Reset Severities" buttons clear your overrides. Open it from the Drift Tools sidebar ("Configure Rules") or the command palette. Changes save to your workspace settings and re-run analysis immediately.
- Startup banner now explains LAN-IP access. With the secure default (
loopbackOnly: true), the banner states that connecting by the device's network IP is off and how to turn it on (loopbackOnly: false+ anauthToken). When you do bind a non-loopback interface, the banner prints the reachablehttp://<lan-ip>:<port>URL(s) beside the existingadb forwardhint, so a Wi-Fi-by-IP user gets a copy-paste address instead of guessing the device IP. GET /api/healthadvertises the bind mode via a newloopbackOnlyfield. A remote client (e.g. Saropa Lints) can now tell "server up but loopback-only" from "no server" — previously both looked like a bare connection-refused.- Clicking "Offline" in the Database sidebar now opens a live connection panel. The "Offline — cached schema" (and "Disconnected") row used to do nothing when clicked. It now opens the Troubleshooting panel showing your actual state: a status banner with the precise next step (start a debug session, or — if one is already running — check that the app calls
DriftDebugServer.start()and is a debug build), plus a configuration grid (target host/port, discovery range, debug-session status, offline-cache setting) above the existing setup guidance. - "Good to know" explainers in the connection panel. A new collapsible section answers the questions that previously lived only in code comments and the changelog: why the server is private to your machine, what "Offline — cached schema" means, why the app must be a debug build, why your Wi-Fi debug port keeps changing, and whether it reconnects after a hot restart.
Fixed #
- Wi-Fi-by-IP debugging looked like a dead server. Reaching the debug server by a physical device's LAN IP failed silently under the loopback-only default, with nothing in-product explaining that the IP route is closed by design. The banner and health endpoint now make the bind mode and the two supported access paths explicit. Documentation-only on the security side — the loopback-only default is unchanged.
- Toggling a rule in the Drift Advisor Rules sidebar errored out. Clicking a rule (e.g. "no-primary-key") to mute it failed with "…is not a registered configuration" because the settings the extension reads and writes —
driftViewer.diagnostics.disabledRules,driftViewer.diagnostics.severityOverrides, anddriftViewer.logVerbosity— were never declared in the manifest, so VS Code refused to save them. All three are now registered, so muting/unmuting rules, severity overrides, and the Set Log Verbosity command write successfully. - Repeated "no longer responding" popups while Wi-Fi debugging. On a flaky link the debug server drops and reconnects over and over, and each cycle used to fire a "Drift debug server on port … is no longer responding" warning plus a "detected" toast on recovery — a steady stream of popups to dismiss. Now you get at most one "lost" warning per debug session: a brief blip that recovers within a short grace window produces no popup at all, the first sustained drop warns once, and after that the session stays silent no matter how many times the connection flaps. Starting a new debug session or running Retry Discovery re-arms the single warning. Disconnect detection is unchanged, so the sidebar/status still reflect the connection state in real time.
Maintenance
- Publish pipeline runs only the affected tests, selected by import graph.
scripts/modules/dart_build.pyrun_testsdiffs the working tree against the last release tag, builds the package's transitive import graph, and runs every*_test.dartwhose dependency closure includes a changed file (resolving relative andpackage:imports, including multi-line conditional exports). This is the "outdated tests" set the editor's Test Explorer shows, computed without the editor — so a change to a core file with no same-named test still runs every test that imports it through any chain. A changed library file that no test reaches is logged as a genuine coverage gap. The only full-suite paths are unreadable git history and an explicitPUBLISH_FULL_TESTS=1;PUBLISH_TEST_BASELINE=<rev>overrides the diff baseline.
4.1.6 #
Internal code cleanup only — no user-facing change. log
Maintenance
- Modularized six extension source files that exceeded the line-count gate (production cap 300 lines, test cap 500). Each original file stays the public entry point — importers and tests are unchanged — and the extracted logic moved to a sibling file following the existing
-helpers/-checkssplit convention:saropa-lints-diagnostics.ts(303) → pure report parsing/mapping (severity map, JSON parse, per-file diagnostic mapping, interfaces) moved to newsaropa-lints-report.ts; the original re-exports them so the test imports still resolve.dashboard/dashboard-css.ts(302) → widget-content and modal styles moved to newdashboard/dashboard-css-widgets.ts, appended bygetDashboardCss.diagnostics/diagnostic-manager.ts(376) → diagnostic-building/suppression filtering and the inline-suppression quick-fix builder moved to newdiagnostics/diagnostic-apply.ts(buildDiagnosticsByFile,buildSuppressionQuickFixes).diagnostics/providers/data-quality-provider.ts(316) → data-skew and null-rate check logic plus the null-by-design / SQL-probe helpers moved to newdiagnostics/providers/data-quality-checks.ts; the provider now only holds the VS Code wiring.er-diagram/er-diagram-script.ts(320) → the webview event-handler block (drag/pan/zoom, context menu, toolbar, filters, message/resize listeners) moved to newer-diagram/er-diagram-script-events.ts, concatenated into the same IIFE alongside the existing helpers.test/data-quality-provider.test.ts(512) → the sharedcreateContextfixture moved to newtest/data-quality-test-helpers.ts, and theprovideCodeActionssuite moved to newtest/data-quality-provider-actions.test.ts.- Verified:
tsc --noEmitclean and the full test suite (2905 tests) passes.
4.1.5 #
A quick fix to stop the debug server from printing its startup banner twice in your logs, plus a few behind-the-scenes dependency updates. log
Fixed #
- Duplicate "DRIFT DEBUG SERVER" startup banner. When
DriftDebugServer.start()was called twice in quick succession (or concurrently), both calls bound the same port and printed the startup banner, so the banner appeared twice in the logs. The "already running" check now also covers a start that is still in flight, so only one banner is ever printed.
Maintenance
- Re-entrancy guard in
_DriftDebugServerImpl.start. The running-state guard tested_server, which is assigned only after the awaits instart(loadPersistedSnapshots,HttpServer.bind). A second concurrent/rapidstart()passed the guard while the first was still binding; withshared: true(SO_REUSEPORT) both binds succeeded and both printed the banner. Added a synchronousbool _startingflag set before the first await and cleared in afinally; the start body moved to a private_startInternalso the flag is cleared on every exit path (return, throw, or successful bind). File:lib/src/drift_debug_server_io.dart. - Dependency upgrades (Dependabot). TypeScript
5.9.3→6.0.3(root andextension/);sass1.99.0→1.101.0(root);js-yaml4.1.1→4.2.0(extension/);mocha11.3.0→11.7.6(extension/); CIactions/checkout6→7. TypeScript 6 (a major version) was confirmed to type-check both the extension (tsc -p ./) and the root web bundle (tsconfig.web.json) with zero errors, and the extensioncompilestep (tsc+ NLS verify + NLS coverage) passes on it. Dev/build dependencies only — no change to shipped runtime behavior. @types/vscodekept at^1.115.0. Dependabot's group bump raised it to^1.125.0, butvsce packagerejects@types/vscodenewer thanengines.vscode(^1.115.0) — the type definitions must not promise APIs beyond the minimum supported VS Code. Pinned back to match the engine so the extension stays installable on VS Code 1.115+.- Publish pipeline now pre-checks
@types/vscodevsengines.vscode. Added a "VS Code API compatibility" quality step (scripts/modules/ext_build.py::check_engines_vscode_compat, wired into Step 7 of the extension pipeline) so a future@types/vscodebump that exceedsengines.vscodefails fast with an actionable message instead of blowing up at thevsce packagestep deep in the run.
4.1.4 #
Snapshots, branches, hovers, and the lineage/impact tools now work against databases whose tables have no rowid — including PowerSync, which exposes its tables as views and uses WITHOUT ROWID system tables. log
Fixed #
- "no such column: rowid" on PowerSync and other rowid-less databases. Several features ordered or keyed table rows by
rowid, but views andWITHOUT ROWIDtables (such as PowerSync'sps_updated_rows) have norowidcolumn, so those reads threwno such column: rowidand the feature failed. Snapshot, branch, snapshot-diff, and hover previews now order by each table's primary key (or omit ordering when none is declared), and the lineage, impact, global-search, mutation-stream, constraint-validator, and data-narrator tools key rows by the primary key — or anidcolumn on a view — instead ofrowid. (#32) - Far fewer noise warnings from the null-rate / unused-column checks. Run against a live debug database, these checks flagged hundreds of columns that are NULL on purpose — event timestamps like
blocked_at, phonetic search helpers, columns with a declared default — and every column on demo-only or partially-loaded tables, where a null rate measured on a handful of rows means nothing. The checks now skip columns that are null-by-design and let you mark unrepresentative tables with the newdriftViewer.diagnostics.userDataTablessetting, while still surfacing genuine content gaps on fully-loaded tables.
Added #
driftViewer.diagnostics.userDataTablessetting. List the tables whose live debug rows are not representative of production (user/demo data, or static reference tables that load lazily). Null-rate and unused-column analysis is skipped for them.
Maintenance
- Null-rate false-positive suppression (
BUG_data_quality_null_checker_false_positives).data-quality-provider.ts_checkHighNullRatesnow skips (a) whole tables inconfig.userDataTables(FP-1, unrepresentative live data) and (b) null-by-design columns via_isNullByDesign(FP-2): columns declared.withDefault(...)/.clientDefault(...),.autoIncrement(), or nullable with a*_at/*_phoneticname suffix. The Dart parser now captures defaults — new optionalIDartColumn.hasDefaultset fromHAS_DEFAULT_REindart-parser.ts. New config plumbing: optionalIDiagnosticConfig.userDataTables, read indiagnostic-config.ts, contributed asdriftViewer.diagnostics.userDataTables(array) inpackage.json+package.nls.json(regeneratednls-coverage-data.ts, 250 keys). Test helpercreateDartFileaccepts per-column declaration overrides (MockColumnSpec); added parser tests forhasDefaultand provider tests for both FP classes plus over-suppression guards (non-nullable*_atand a plain high-null column on a representative table still report). Full suite 2905 passing. - rowid-free SQL helpers. Two new helpers under
extension/src/sql/:samplingOrderBy(pkColumns, descending?)returns anORDER BYover the declared PK (always valid, including forWITHOUT ROWIDtables, which SQLite requires to declare a PK) or an empty clause when no PK exists;rowKeyColumn(columns)picks a row-identity column preferring the PK, then a literalidcolumn (the PowerSync table-view case), thenrowidonly as a last resort. Applied to the sampling sweeps (timeline/snapshot-store.ts,branching/branch-manager.ts,timeline/snapshot-commands.ts,hover/drift-hover-provider.ts) and to the keyed-operation sites (lineage/*,impact/*,global-search/global-search-engine.ts,mutation-stream/mutation-stream-panel.ts,constraint-wizard/constraint-validator.ts,narrator/narrator-commands.ts). The hover preview now fetches schema metadata before its data read so the order clause can use the PK. Addedtest/sampling-order.test.ts,test/row-key.test.ts, and a rowid-less-sweep regression test intest/snapshot-store.test.tsthat asserts no emitted sweep referencesrowid; full suite 2897 passing.
A fix so the new Rules sidebar can't error out while the extension is reloading. log
Fixed #
- "No view is registered with id: driftViewer.rules" on activation. The Drift Advisor Rules view used an eager registration call that throws if the editor hasn't re-read the extension manifest yet (which happens right after a reload), and that error could interrupt the rest of diagnostics setup. The view now registers with the tolerant API that never throws and simply wires up once the view is available.
Maintenance
- Rules view registration hardened.
extension-diagnostics.tsnow callsvscode.window.registerTreeDataProvider('driftViewer.rules', …)instead ofcreateTreeView.createTreeViewresolves the view eagerly and throws "No view is registered with id" when the loaded manifest lacks the contribution (JS reloaded beforepackage.jsonwas re-read), which aborted the remaining provider/command registrations insetupDiagnostics.registerTreeDataProviderdoes not validate the id at call time and theTreeViewhandle was unused. AddedregisterTreeDataProviderto thevscodetest mock (vscode-mock.ts); full suite 2883 passing.
4.1.2 #
Silence advisor findings right in your Dart source — one column or a whole file — and manage every rule from a new sidebar that shows how noisy each one is and lets you mute it in one click. log
Added #
- In-code suppression. Silence a finding from the Dart source, the way Dart's own
// ignore:works, with a dedicated marker:- Field level:
// drift-advisor:ignore high-null-rateon the line above a column getter (or as a trailing comment) silences that code for that column. - File level:
// drift-advisor:ignore-file high-null-rateanywhere silences that code for the whole file. - The code list is optional — a bare
// drift-advisor:ignore/ignore-filesilences every advisor code — and accepts several comma- or space-separated codes.
- Field level:
- One-click "Ignore" quick fixes. Every advisor finding's lightbulb now offers "Ignore … for this column" and "Ignore … in this file", which insert the right directive for you — no typing, and the finding clears immediately.
- A "Drift Advisor Rules" sidebar. A new view lists every rule grouped by category with its live finding count and on/off state, noisiest first. Click a rule to mute or un-mute it everywhere — the fast way to tame a workspace with hundreds of findings without hand-editing settings.
Maintenance
- Inline suppression engine. New
diagnostics/suppression.tsparses// drift-advisor:ignore[-file]directives (CRLF-safe, case-insensitive marker, codes lowercased; full-line directive targets the next non-blank line, trailing directive targets its own line).IDartFileInfogains asuppressionsfield populated indart-file-parser.ts;DiagnosticManager._applyDiagnosticsindexes suppressions by file URI and skips file-level and field-level (line-matched) hits centrally — no per-provider changes, so it covers every column-/table-scoped diagnostic automatically. - Suppression-insert commands.
diagnostics/suppression-commands.tsaddsdriftViewer.suppressDiagnosticInColumn/…InFile, registered inextension-diagnostics.tswith a refresh callback so the parser (which reads in-memory document text) honors the new directive before save. The two quick-fix actions are appended to every advisor diagnostic inDiagnosticManager.provideCodeActions. - Rules tree view.
diagnostics/rules-tree-provider.tsrendersDIAGNOSTIC_CODESgrouped by category with live counts from a newDiagnosticManager.getCollectedCountsByCode(); a newonDidRefreshevent re-renders it after each cycle.driftViewer.rules.toggleRulewritesdisabledRules;driftViewer.rules.refreshis a view title button. Registered thedriftViewer.rulesview + four commands inpackage.jsonwith NLS titles; regeneratednls-coverage-data.ts. - Tests.
suppression.test.ts(field/file/trailing/bare/multi-code/CRLF/case parsing); updated fourIDartFileInfoconstruction sites and two code-action tests for the new actions; activation disposable count 232 → 238. Full suite 2883 passing.
For versions 4.1.1 and prior, see CHANGELOG_ARCHIVE.md.
