feat(history): fold the roadmap, lay out the sidebar, highlight what you click - #271
Merged
Merged
Conversation
…tor syntax testing turned up **Blueprint** now carries a `#` column in the column and index tables. "Column id" is the ordinal in every catalog that exposes one under that name (Oracle COLUMN_ID, SQL Server column_id), and it needs no new introspection — the compare already builds the column list in the source table's own order. The number comes from the *unfiltered* diff, not the rendered row index: numbering visible rows would renumber them the moment "show unchanged" is off, so column 7 would read as 2 — a worse lie than showing nothing. **Editor syntax across dialects**, tested rather than assumed (`dialect-syntax.test.ts`). What already works: MySQL DELIMITER blocks, Oracle `/` terminators, Postgres dollar-quoted bodies, semicolons inside bracket, backtick and string literals, and write detection for REPLACE / COPY / LOAD DATA / SELECT INTO / MERGE / TRUNCATE. One real fix: an anonymous `BEGIN … END` block read as a **non-write**, so Safe Mode ran it without a confirmation. That is the shape this repo itself generates for Db2 and Oracle tolerant drops, and the statement inside usually sits in an `EXECUTE IMMEDIATE '…'` literal the scanner strips on purpose — so it fails closed now. `BEGIN;` / `BEGIN TRANSACTION` stay quiet, since a confirmation on those would only teach people to click through. The RBAC gate was already correct here (its allowlist is fail-closed); this was the confirmation dialog only. Monaco now gives the wire-compatible relatives their family's grammar — MariaDB/TiDB get MySQL, CockroachDB/YugabyteDB/Redshift get PostgreSQL — instead of falling through to generic `sql` and losing backtick and dollar-quote handling. Two gaps are recorded as `it.fails` rather than quietly left: T-SQL `GO` is not a terminator the splitter knows (a GO script arrives as one statement and the server rejects it), and a bare `BEGIN … END;` block is chopped into fragments. Both need the dialect, which `splitSqlStatements` does not take; `BEGIN` cannot simply become an opener because it starts a transaction in Postgres and MySQL. They will turn red when fixed, which is the signal to drop the `.fails`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ull screen **One control per concept.** The sidebar carried four panels for two ideas: a "Legend" that only showed the object-type colours, an "Object status" list that only showed the status colours, and separately an "Object type" checkbox list and a "Status filter" button row that did the actual filtering. The colour key was never where the click was. Now there are two: **Object type** and **Object status**, each row carrying its own dot and doing the filtering when clicked. Verified in the browser — clicking Deleted takes the graph from 14 nodes to 11 and back. The type rows stay real checkboxes on purpose: `schema-history.test.ts` asserts `isChecked()` on six of those testids, and turning them into buttons would have broken the suite for a cosmetic reason. **Script pane gets a maximize button.** The inline diff is capped at `max-h-56` because it sits in a detail column beside everything else about the object, which is fine for a short table and useless for a view or a routine body. The header now has a maximize button that reopens the same diff full screen (Esc or the backdrop closes it). It lives in `GithubScriptDiff`, so the object inspector and the version compare modal both get it without either growing its own copy — and the line renderer is shared between the two sizes rather than duplicated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…mits Identifiers have had plenty of attention this week; column DEFAULTs and type specs had none. Both go into the DDL almost verbatim, so a sweep of hostile defaults (quotes, semicolons, comment markers, backslashes, unicode) and edge type specs ran against Postgres, MySQL, SQL Server, Db2 and Oracle. The good news first: escaping holds. Every engine round-tripped `x'; DROP TABLE victim; --` as data, including apostrophes, `--`, `/* */` and unicode. Nothing executed out of a literal. **MySQL lost backslashes.** `information_schema` reports a string default as a bare value, so the provider builds the literal — and it doubled only the apostrophe. MySQL treats `\` as an escape inside a string, so a default of `C:\path\name` was re-emitted as `'C:\path\name'` and stored as `C:path` plus a newline. Nothing failed; the migrated column just quietly had a different default from the source. Proven both ways through the provider path: without the fix `"C:\path\name"` became `"C:path\name"`, with it the copy matches the source exactly. **Db2 accepted a DECIMAL precision it cannot store.** Postgres, Oracle and SQL Server all allow 38 digits; Db2 stops at 31 and answers SQL0604N from 32 up (verified on 11.5). The source precision passed straight through, so a cross-dialect migration into Db2 failed at CREATE TABLE — after the plan had been reviewed and accepted. It is clamped now, with a warning naming the narrowing rather than doing it silently. Two results were **my harness, not the product**, and are worth writing down so they are not rediscovered as bugs: * SQL Server appeared to mangle `café ☕` into `café ?`. It does — for a plain `'…'` literal. But the provider carries `sys.default_constraints.definition` through verbatim, so a source default created as `N'…'` keeps its prefix. The sweep had built the literal by hand. * A "LEAK" flag fired on every Db2 case. The probe was a bare `SELECT 1`, which Db2 rejects outright (SQL0104N) — it needs `FROM SYSIBM.SYSDUMMY1`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…losing columns `SELECT 1, 2` showed **one** column. The grid derives its columns from the keys of each row object, so two result columns arriving under the same key collapse into one and the other disappears — silently, with no error anywhere. Measured against the live engines, this is a per-dialect problem: | Dialect | unaliased expression is called | effect | |------------|--------------------------------|--------| | Postgres | `?column?` — every one | all collapse into one | | SQL Server | `''` (unnamed) | all collapse, blank header | | MySQL | the expression text | collides when two match | | Db2 | positional `1`, `2` | fine | | Oracle | de-duplicated `1+1`, `1+1_1` | fine | `autoAliasSelectColumns` names them in the SQL before it runs, so every engine returns distinct keys and the header reads like the query: `count(*)` becomes `count`, a second one `count_2`, a lone literal `literal`, anything else `col_N`. Generated names never shadow an alias the user already wrote. It is deliberately conservative and hands the statement back untouched whenever it is not a plain SELECT, when the select list will not parse, or when nothing needs naming — plain and qualified columns, `*`, and existing explicit or implicit aliases are all left exactly as typed. Half-written SQL is normal in an editor, and mangling somebody's query is far worse than a missing grid column. Verified through `runStatements`, the path the editor actually uses: without the change Postgres reported `["?column?"]` for three of four cases; with it all five engines return every column. The parser is scanned rather than regexed — the first version had three patterns eslint's security plugin flagged as ReDoS-prone, which matters when the input is whatever the user has typed so far. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…you click Five changes to the Lokee history surface, plus a friendly SQLite file picker. Roadmap folding. A database captured over months has hundreds of versions and any one table moved in a handful of them; printing one row per version buried the few that mattered. `roadmap.ts` keeps the versions that changed the object, the head, and whatever is being viewed, and folds each untouched run into one row that opens on click. Rows gain a column delta measured against the real previous version — not the previous visible row, so collapsing a gap cannot change the number. Sidebar. Object type leads (it decides what the graph is made of; every other filter reads as a narrowing of it), then status, version, date, user. Sections drag to reorder by their grip, the column drags to resize, and both persist per browser. Version and User lists carry a permanently visible scroll track — macOS hides overlay scrollbars, so a list that scrolled looked identical to one that had been cut off. Click-to-highlight, and the wiring bug under it. The node renderers already drew a ring for a `selected` node, but the graph is fully controlled with no `onNodesChange`, so React Flow's selection had nowhere to be written back and `selected` was never true. `highlight.ts` applies selection from app state, and lights the clicked object's whole lineage — one object owns one column for its life, which is what the graph is for — fading the rest rather than hiding it. The same missing `onNodesChange` was silently breaking edges. React Flow's `parseHandles` drops a node's handle bounds when handed a replacement object without `measured`, and `isNodeInitialized` then refuses to place any edge touching it. Rebuilding on every filter change reset those bounds with no channel for the re-measurement to return. Fixed with `useNodesState` + `onNodesChange`, and `carryMeasurements` so a rebuild does not drop the edges for a frame. Reverting leaves the object inspector. It lives in the version compare modal, which can scope a revert to ticked objects; a per-row button could only ever do the whole schema, and two entry points with different blast radii was the trap. The "Reused hash — stored once (pointer)" line goes too — storage internals. SQLite/DuckDB credentials. Host, port, username, password and SSL are hidden for a file on disk, "Database Name" becomes "Database File", and Browse… opens a picker. A browser's file dialog yields a name and no path, so the picker lists the machine running the backend: `GET /api/files/browse` returns directories and only *.db/*.sqlite/*.sqlite3/*.duckdb, with name, size and mtime — never contents — behind `schema.browse` and rate-limited. Deliberately not jailed to a root: a signed-in user can already open any path by typing it, and the extension filter is what keeps this from being a general file browser. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_59db89e7-9fba-4299-9680-8900b3611a57) |
huyplb
added a commit
that referenced
this pull request
Aug 18, 2026
…o longer has Fallout from #271, already on main. Hiding host / port / username / password for file dialects was right for the product and broke every e2e that saves a SQLite credential: `addSqliteCredential` fed `conn-host-input` a value, and Playwright sat waiting 30s for a field that is not rendered. `schema-revert.test.ts` failed in `beforeAll` and skipped all four cases. The helper now passes only what a file dialect shows — a path — and `addCredential` fills host, port, username and password only when the caller supplies them, so live-server dialects are unaffected. This gets the suite past setup and into the tests. It is not green yet: the target picker does not end up with the new credential selected, so `snapshotTarget` finds its button still disabled. Root cause not yet found — recorded here rather than left as a silent red. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
huyplb
added a commit
that referenced
this pull request
Aug 18, 2026
… and say it in the reader's words (#273) * fix(lokee): a revert only runs against the current database, and snapshots first Three changes to the revert path, one of them a correctness bug. **What you review is now what runs.** The diff came from `compareLokeeVersions(original, target)` while the plan and the execute call went to `planLokeeRevert(original)` — which reverses from the *live head*, ignoring Target entirely. With an older version on Target the reader reviewed one script and Execute applied a different, usually larger one. Reverting restores the live database, so it is only coherent when Target is the newest version; anything else is refused, with a one-click "Use current database" to fix it. An open dialog now follows the picker instead of holding the pair it opened with, so that button visibly does something. **Snapshot before touching anything.** The revert route captures the live schema first. That leaves a version to come back to, but the reason that matters is correctness: `planRevert` reverses from the newest *captured* version, not from what is in the database, so a schema edited by hand since the last capture was being reversed against a picture that no longer existed. When the snapshot finds drift the request is refused (`schema_drifted`) rather than applied — the caller reviewed a plan built on the old head, and running a different one silently is the surprise this exists to stop. Verified live: a hand-added column is captured as its own version and the revert leaves the database untouched. **The version menus say which choices cannot be reverted.** The newest version on Original reads "current, nothing to restore"; an older version on Target reads "compare only". Both stay selectable — they are legitimate comparisons — but they no longer look identical to a choice that can run and then dead-end at a greyed-out button with the reason hidden in a tooltip. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat(history): say it in the reader's words, and stop showing filters that do nothing A launch-readiness pass over the schema history module, reading it as someone seeing it for the first time. **One real bug, found by reading a card.** A deleted column came back from the graph DTO typed as `table` — `objectType: row.object_type ?? 'table'`, and the delta row carries no type. So a deleted column was drawn with a table's icon and colour, and slipped through the default table filter as a phantom table. The key already says what it is (`column:ORDERS.NOTE`), so the fallback now reads the kind from the key. **Names the database actually uses.** Children were labelled with their compare key — `ORDERS.NOTE` — which CLAUDE.md is explicit is an uppercased match key and never an identifier. Cards now read `NOTE` with `deleted from orders` beneath. **Filters that can change what you see.** The object-type list was the union of every dialect, so a SQLite user got MQT — a term only Db2 uses — beside Procedure and Function boxes that could never match. It now offers the types the history contains; a type the user ticked themselves stays. On the SQLite demo that is 8 checkboxes down to 2. **Words, not internals.** "reused" was the store's vocabulary for one object pointed at by many versions; a reader is asking what moved. Summary reads "12 unchanged", the legend "Unchanged", the edge key "Unchanged from previous version". "Content-addressed schema history (Lokee)" — an internal codename and an implementation detail — is now what the feature does. The header says Schema history everywhere. **A first run you can act on.** The empty state was a paragraph pointing at a button on another bar: the one screen where a newcomer has nothing to act on was the one screen with no action on it. It now holds the database picker and a "Take first snapshot" button. Also `[sqlite] /tmp/app.db.main` → `sqlite · /tmp/app.db · main`; the dotted schema suffix read as a file extension. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(lokee): stop crying data loss over a catch-up, and name where a run goes The last launch blockers in schema history, all found by walking the flow the user described — a database that has fallen behind, brought back up to date. **The loudest warning was on the safest operation.** `ReversalRisk` defines `lossy` as "succeeds but destroys or truncates data". Re-creating a dropped column does neither: it adds one back, empty, because the rows went when it was dropped. Classifying it lossy meant a plan of nothing but ADD COLUMN came back `risk: lossy`, showed "This revert destroys data", and demanded a data-loss acknowledgement — on the single most common thing anyone would do with version history. It is now `safe`, and keeps the note explaining the column comes back empty, because that is worth knowing while the gate is not. Verified against a live database three versions behind: the plan went from `risk: lossy, lossyCount: 2` to `risk: safe, lossyCount: 0`, and executes without a confirmation, restoring both columns. **The button says where it goes.** "Execute migration (3)" gave a count and no destination, and read as an undo even when the plan only adds. Direction cannot come from version numbers — the plan always runs from the head, so the target is always the lower number — so it comes from what the plan does: a plan that destroys nothing is catching a database up ("Update to v2"), one that destroys something is rolling it back ("Revert to v2"). **The author line earns its place.** It answered "who did this?" only when the versions disagreed; on a single-user install it was the same address repeated down the whole column, crowding out the date and change count that do differ. Shown only when the versions actually have different authors. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(licensing): hide the React Flow badge, and ship the notices that are actually required No Pro subscription, so the on-canvas "React Flow" badge is gone. That is permitted: @xyflow/react is plain MIT (node_modules/@xyflow/react/LICENSE), and its only condition is that the copyright and permission notice accompany the software — displaying a badge in the UI is not a licence term. xyflow asks that you subscribe when you hide it; that is a request, and this project has chosen not to. Which leaves the condition that *is* real, and was not being met. The client bundles 38 third-party packages — React Flow among them — under MIT, BSD and ISC, all of which require their notice to travel with the code. `NOTICE` only carried Fox Schema's own Apache-2.0 terms, so shipping a build distributed their code without their notices. - `THIRD-PARTY-NOTICES.md` reproduces each package's own licence text, generated from the installed tree by `scripts/generate-third-party-notices.mjs` and regenerable when dependencies change. - `.gitignore` blanket-ignores `*.md`, which would have kept the file out of the repo entirely; added to the tracked exceptions beside README and SECURITY. - The Dockerfile copies LICENSE, NOTICE and the new file into the image, so the notices reach whoever runs the container rather than stopping at the repo. Only gap: @google-cloud/secret-manager (Apache-2.0) ships no licence file of its own; its entry names the licence and links upstream. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(e2e): the credential helper still filled a host box that SQLite no longer has Fallout from #271, already on main. Hiding host / port / username / password for file dialects was right for the product and broke every e2e that saves a SQLite credential: `addSqliteCredential` fed `conn-host-input` a value, and Playwright sat waiting 30s for a field that is not rendered. `schema-revert.test.ts` failed in `beforeAll` and skipped all four cases. The helper now passes only what a file dialect shows — a path — and `addCredential` fills host, port, username and password only when the caller supplies them, so live-server dialects are unaffected. This gets the suite past setup and into the tests. It is not green yet: the target picker does not end up with the new credential selected, so `snapshotTarget` finds its button still disabled. Root cause not yet found — recorded here rather than left as a silent red. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(lokee): drop an unused historyCompare import Flagged by the code-quality bot on #273. Pre-existing rather than new — the symbol had no reference before this branch either; the file being in the diff is what surfaced it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
huyplb
added a commit
that referenced
this pull request
Aug 19, 2026
…wn family, and SQLite credentials selectable again (#274) * fix(sync): selecting a saved SQLite connection asked for a password it cannot have A real break, found by running the revert e2e before releasing rather than after. `selectSavedConnection` treats "no stored password" as "prompt the user", which was never reached for SQLite while the credential form still offered a Save-password box. #271 hid that box for file dialects — correctly, a file has no password — so SQLite credentials now save with `hasPassword: false`, and picking one as Source or Target opened a password prompt, snapped the picker back to "— Saved —", and left no connection selected. Snapshot, Compare and Migrate all stayed disabled with nothing on screen explaining why. Reproduced by hand in the browser, not inferred from the test: pick a saved SQLite credential, watch the select revert. `isFileDialect` / `dialectUsesPassword` now live in `provider-settings` beside the rest of the per-dialect truth, and both the picker and the connection modal read them instead of each carrying their own copy of the list. Also updates the last e2e that asserted the old filter behaviour: it checked `isChecked()` on Function and Procedure boxes, which this seed's schema does not have and which are therefore no longer rendered — an assertion on an absent control hangs rather than fails. E2E after this, against the running app: schema-revert 4/4, schema-version-revert-edges 10/10, schema-history 6/6. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(utilities): index fragmentation named the wrong Postgres function, and MariaDB is not MySQL Two reported failures, both reproduced against the live engines rather than reasoned about. **Index fragmentation on PostgreSQL could never have worked.** The probe read `SELECT leaf_fragmentation FROM pgstattuple(ci.oid)`. Against PostgreSQL 17: -- extension absent (the default, and the reported error) ERROR: function pgstattuple(oid) does not exist -- extension installed — the obvious fix ERROR: column "leaf_fragmentation" does not exist `pgstattuple` reports *table* statistics. `leaf_fragmentation` belongs to `pgstatindex`, which returns 0 for a real index. So installing the extension swapped one error for another; the feature was broken either way. Now uses `pgstatindex(ci.oid::regclass)`, verified returning values on that server. Two follow-ons. `pgstatindex` yields NaN for an index with no leaf pages yet — "nothing measured", not a number, and `NaN%` in a column reads as a bug — so it is nulled out. And a missing extension now explains itself: the panel says the server needs `CREATE EXTENSION pgstattuple;` and that a superuser can add it, instead of relaying the driver's sentence. **MariaDB was aliased to the MySQL family and two probes were wrong on it.** Against MariaDB 11.8: SELECT @@innodb_buffer_pool_instances; -- ERROR 1193: Unknown system variable SELECT ... FROM performance_schema.global_status ... -- (empty) SELECT ... FROM information_schema.GLOBAL_STATUS ... -- 245412 The removed variable killed System info outright, which is the reported bug. The empty one was quieter and worse: performance_schema is off by default on MariaDB, so the pool panel showed blank connection counts with no error at all. MariaDB is now its own family, reading status from `information_schema` and dropping the variable it no longer has; sessions and sizes share the MySQL queries, which do work on it. Verified live, all four utilities: pool 151 max / 1 connected, sessions listed, system up 245536s on 11.8.8-MariaDB, sizes reported per table. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Five changes to the Lokee history surface, plus a friendlier SQLite credential form. The last two commits carry over from the previous branch (#267 merged only the Db2 commit from it).
Roadmap folding
A database captured over months has hundreds of versions, and any one table moved in a handful of them. Printing one row per version buried the few that mattered.
roadmap.tskeeps the versions that changed the object, the head, and whatever is being viewed; each untouched run folds into one row (8 versions left it unchanged · v2–v9) that opens on click.Show all versionsunfolds everything. Rows gain a column delta measured against the real previous version — not the previous visible row, so collapsing a gap cannot change the number.Verified on a seeded 15-version SQLite history where
customersmoved at v1 and v10: 5 rows instead of 15; expandingv2–v9gives 11; "Show all versions" gives 15.Sidebar layout
Order is now Object type → Object status → Version → Date → User. Object type decides what the graph is made of, so every other filter reads as a narrowing of it; status is the legend and sits next to what it colours.
Sections drag to reorder by their grip (only the grip — the panels are full of checkboxes and date inputs, and a whole-panel drag source turns every mis-aimed click into a drag). The column drags to resize; double-click resets, arrows nudge. Both persist in
localStorage.Version and User lists carry a permanently visible thin scroll track: macOS hides overlay scrollbars, so a list that scrolled looked identical to one that had been cut off.
Click-to-highlight — and the wiring bug under it
The node renderers already drew a ring for a
selectednode, but the graph is fully controlled and had noonNodesChange, so React Flow's selection had nowhere to be written back andselectedwas never true. Clicking opened the inspector and the graph looked untouched.highlight.tsapplies selection from app state: solid ring on the clicked card, dashed outline on the same object at every other version, bright animated lineage edges, and everything else faded to 0.28 — faded, never removed, so the graph does not re-flow on a click. With no selection it returns the same array references, so the common case costs nothing.The same missing handler was breaking edges
From React Flow's source,
adoptUserNodes→parseHandles:isNodeInitializedthen refuses to place any edge touching a node with no handle bounds. This graph handed React Flow freshly built node objects (nomeasured) on every filter change, with noonNodesChangeto report the re-measurement back — so each rebuild reset the bounds with nothing to restore them.Fixed with
useNodesState+onNodesChange, pluscarryMeasurements(next, previous)so a rebuild does not drop the edges for a frame. Seven unit tests pin the contract, quoting the React Flow source as the reason.Reviewer note, stated plainly: I first reported this as "edges intermittently vanish" from browser observation. That symptom was substantially a harness artifact — my Browser pane was hidden, so the container measured 0×0 and nothing could be measured (the
NaNconsole spam on<circle>/<pattern>is the same zero-size layout). With the pane painted, edges draw. The wiring defect above is real and verified against the library source; the browser could not give a clean A/B while pane visibility was doing its own thing, so the evidence here is the unit tests.Reverting leaves the object inspector
It lives in the version compare modal, which can scope a revert to ticked objects. A per-row button could only ever revert the whole schema, and two entry points with different blast radii was the trap. The
Reused hash — stored once (pointer)line goes too — storage internals, not schema history.SQLite / DuckDB credentials
Host, port, username, password, SSL and "Save password" are hidden for a file on disk; "Database Name" becomes Database File; the default credential name is the file rather than
localhost/.Browse…opens a picker. A browser's file dialog yields aFilewith a name and no path, so it lists the machine running the backend:GET /api/files/browse— directories plus only.db .db3 .sqlite .sqlite3 .duckdb .ddb, with name/size/mtime and never contents. Dotfiles skipped, 500-entry cap, rate-limited 60/min, behindschema.browse.file-browse.test.tsassertsid_rsa,secrets.env,dump.sqland.envare never listed. If you want the jail, it is one env var plus a prefix check inresolveBrowsePath.Verified live: picking
/tmp/fox-roadmap-demo.dbfills the field, andPOST /api/schema/listagainst it returns{"schemas":["main"]}.Gates
cd apps/web && npx tsc --noEmitclean ·npx vitest run1925 pass / 2 expected-fail · eslint 0 errors. The threedetect-non-literal-fs-filenamewarnings infile-browse.tsare the feature itself and carry inline justifications.🤖 Generated with Claude Code
Note
Medium Risk
Adds a filesystem listing API (extension-filtered and permission-gated, but not path-jailed) and changes SQL rewriting, migration type rendering, and graph/React Flow wiring—areas that affect security posture and execution behavior if misconfigured.
Overview
Lokee history gets a folded object roadmap (changed versions, head, and current view stay visible; unchanged runs collapse into expandable gaps with column deltas vs the real prior version), a reorderable/resizable filter sidebar (persisted in
localStorage), and selection highlighting on the version graph (clicked object/version, lineage edges, dimmed rest) wired throughuseNodesState,onNodesChange, andcarryMeasurementsso edges do not disappear after filter rebuilds. Per-row revert is removed from the object inspector (revert stays in the compare modal); script diffs can open full screen.SQLite / DuckDB credentials hide server-style fields and add Browse… backed by
GET /api/files/browse(schema.browse, rate-limited): directories plus database extensions only, metadata never file contents, NUL paths fall back to home.SQL execution rewrites unaliased
SELECTexpressions viaautoAliasSelectColumnsso result grids get distinct column keys. MySQL string defaults now escape backslashes innormalizeDefault; Db2 caps migratedDECIMALprecision at 31 with a warning;BEGINprocedural blocks are treated as writes for safe-mode classification. Schema compare blueprint shows # position for columns and indexes; Monaco maps wire-compatible dialects to the right SQL grammar.Reviewed by Cursor Bugbot for commit 50fc6b8. Bugbot is set up for automated code reviews on this repo. Configure here.