Commit Graph

38 Commits

Author SHA1 Message Date
ShiroKSH d4b0eeba9b fix(graph-ui): clear ADR content on delete
Signed-off-by: ShiroKSH <kushidashiro@gmail.com>
2026-07-09 21:31:28 +03:00
Martin Vogel 3eb294fa11 fix(ui): serve the upstream issues URL from the backend
DCO / dco (push) Has been cancelled
The UI security audit (security-ui.sh, layer A1) forbids hardcoded
external URLs in graph-ui source — the callout's GitHub issues link
tripped it on all pr-smoke legs. Follow the established pattern for
external targets (/api/repo-info deep-links): /api/ui-config now
carries upstream_issues_url and the callout consumes it, rendering the
issue button only when the backend provides an https URL. The protocol
check uses a regex literal on purpose: a bare protocol string in source
also aborts the audit's URL extraction.

Verified: security-ui.sh passes locally; /api/ui-config serves the URL;
the callout opens with the prefilled link in the browser; frontend
build + tests green.

Refs #963

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-09 03:38:34 +02:00
Martin Vogel 660b90d94a feat(ui): report-an-edge-case callout when the missed skeleton is selected
Clicking a node of the missed skeleton now opens a dedicated right-panel
callout instead of the standard node panel (code snippet and
callers/callees are meaningless for a not-fully-indexed file):

- explains the gap in plain words (best-effort detection; the file
  content itself is ground truth)
- asks the user to have their agent summarize what fails to parse and
  report it upstream so the edge case can be handled
- two working actions: a prefilled upstream GitHub issue (title
  'Indexing gap: <file>'; body carries ONLY the file path and project
  name, with an explicit add-snippets-only-if-shareable note) and a
  copy-to-clipboard agent prompt (index_status -> summarize flagged
  ranges -> file the issue) with visible copied feedback

Verified in the browser: skeleton click opens the callout with both
actions; frontend build + tests green.

Refs #963

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-09 02:04:58 +02:00
Martin Vogel afc649f4e8 feat(ui): missed skeleton beside the code galaxy with click-to-focus
Render the miss graph side by side with the code graph instead of
swapping layouts:

- /api/layout (code graph) now also attaches "missed_graph": {nodes,
  edges, offset} — the shadow-project layout placed below the primary
  cluster (same satellite pattern as linked_projects; -Y slot so
  cross-repo satellites collide last). graph=missed stays the isolated
  view for API users
- the UI paints the skeleton white and ghostly beside the galaxy,
  auto-frames BOTH clusters on load, and navigates naturally: clicking
  the skeleton flies the camera into it (file labels + detail panel),
  clicking a code node flies back to the code side, and clicking empty
  space while the skeleton has focus returns to the overview (the
  galaxy can be entirely off-screen at that point)
- the sidebar toggle now shows/hides the skeleton (with a missed-file
  count) rather than swapping the layout

Verified in the browser: overview composition, skeleton focus with
split.c labeled, and the empty-space return flight; ui/httpd suites and
frontend build + tests green.

Refs #963

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-08 20:07:01 +02:00
Martin Vogel 664313e4ac feat(index): missed graph in UI + read-time coverage hook + recovery subtraction
Four refinements to the coverage signal:

- extraction: subtract DEFINITE recovery before flagging. Tree-sitter
  error recovery plus the ERROR-descending def walker often re-extract
  constructs inside a failed region (verified: `def broken(:` comes
  back as a def); a region whose every line is covered by definitions
  that START inside it is not a miss, and a fully recovered file is not
  flagged at all. Container defs (Module/Package) don't count as
  evidence, and partially covered regions stay flagged — the
  #ifdef-split case keeps its flag because the first branch's function
  is genuinely lost
- naming: the query_graph option is graph="missed" (the graph shows
  ONLY misses — "coverage" was misleading); shadow project renamed to
  "<name>::missed"; tool descriptions updated so agents discover both
  the option and its semantics
- hook: the CLI-installed PreToolUse augmenter now also matches Read
  and injects a coverage note when the file being read is listed as not
  fully indexed ("line ranges X-Y could not be parsed — the file
  content you are reading is ground truth"). Safe against the old
  issue-362 hazard: the augmenter is structurally non-blocking (always
  exit 0, additionalContext only), mirroring the Gemini matcher that
  already includes read_file; matcher upgrade bookkeeping updated
- ui: "Missed files" toggle in the graph sidebar renders the miss
  graph as a second graph option — /api/layout gains graph=missed
  (same db file, shadow-project scoping; base project name validated
  as before)

Tests: recovery-subtraction cases (recovered def unflagged, garbage
region flagged, trailing recovered defs keep the flag), CLI matcher
tests updated, e2e resilience fixtures switched to an unrecovered
miss; verified end-to-end: hook emits the note for a flagged file and
stays silent otherwise, /api/layout?graph=missed serves the miss graph
and the code graph is unchanged, frontend build + tests green.

Refs #963

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-08 19:13:25 +02:00
Martin Vogel 8e65f8f1be feat(graph-ui): density-aware contrast + Display controls
DCO / dco (push) Has been cancelled
High-density graphs washed out to a cyan-white blob: edges blend additively
and the per-node boost rewarded luminance, so ~80k overlapping edges plus the
white/yellow mid-tier stars saturated the center.

Compensate by density so contrast stays roughly constant as the graph grows:
- edges dim by ~1/sqrt(edgeCount) (they cause the blob) while nodes and bloom
  stay at full strength up to 25k nodes and only ease gently past that, so the
  bright-star look is preserved on moderate graphs;
- the per-node glow is now colour-aware by channel dominance instead of
  luminance: blue hubs (high-degree) glow brightest, red leaves modestly,
  white/yellow least — matching the star-class meaning and giving the graph
  depth instead of a uniform white core.

Add a Display menu (edge brightness / node glow / bloom multipliers, 0–3×,
persisted) layered on top of the adaptive defaults, so the look is tunable
live. A selection is never density-scaled and stays bright against the
dimmed rest.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-07 23:34:22 +02:00
Martin Vogel 9969253bdc feat(graph-ui): type-in node budget with live loading progress
Add a node-budget control next to Refresh: type any count (snapped to
5,000 steps, 5,000 default, 10M max) and the graph reloads with that many
nodes plus every edge between them. The choice persists per project.

Loading gets real feedback: the layout response streams with a live
MB counter under an animated constellation (static under
prefers-reduced-motion), replacing the bare spinner.

Rendering scales with the budget: instance matrices are rebuilt only when
the node set or highlight changes instead of every frame, sphere
tessellation steps down as counts grow, and past 75k nodes the cloud
switches to soft point sprites — one position per node — so six-figure
budgets stay interactive. The truncation notice now names the loaded edge
count and points at the budget control.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-07 23:09:20 +02:00
Martin Vogel c091ffdb05 fix(graph-ui): avoid external-URL literals in doc comments + test fixtures (UI security audit)
DCO / dco (push) Has been cancelled
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-04 23:00:50 +02:00
Martin Vogel ca261d44c1 Merge remote-tracking branch 'origin/main' into distill/789-deadcode-ui
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>

# Conflicts:
#	graph-ui/src/components/FilterPanel.tsx
2026-07-04 22:45:07 +02:00
Martin Vogel a27680c3a9 feat(graph-ui): dead-code filtering, node code preview, GitHub deep-links
Distilled from #789 (safe features only). Adds backend dead-code classification
(status + in_calls in the layout JSON), a GET /api/repo-info endpoint for GitHub
deep-links, and the frontend dead-code filters, node code preview, and deep-links.

The render-cap revert was dropped (kept 2000 for DEFAULT_MAX_NODES/HARD_MAX_NODES
and GRAPH_RENDER_NODE_LIMIT); the sidebar regex-search refactor was omitted.

Security fixes over the original: repo-info strips credentials from any returned
remote_url; the legitimate https blob-URL construction is allow-listed so the
static gate passes; libgit2 is not re-initialized/shutdown per request (reuses the
process-wide init from cbm_alloc_init); deep-link path segments are URL-encoded.

Co-authored-by: Andy Zehady <azehady@ciroos.ai>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-04 22:19:53 +02:00
Martin Vogel 9987a4eae0 Merge pull request #740 from rarepops/feat/graph-ui-routing-and-filter-ux
feat(graph-ui): URL routing, filter panel UX, and camera fly-to fix
2026-07-04 21:36:29 +02:00
Martin Vogel 093eaf17de Merge pull request #839 from sahil-mangla/main
test(graph-ui): fix locale-dependent number-format assertion in GraphTab.test.ts
2026-07-04 19:34:33 +02:00
Zadak c751240dc0 fix(graph-ui): keep the camera on the focused node after fly-to
The fly-to animation moved only the camera and called lookAt, leaving the OrbitControls pivot at the origin, so OrbitControls re-centred the view on the next frame and snapped the camera back once the animation ended.

Lerp the controls target to the focus point as well so the view stays on the clicked node or cluster and orbits around it afterwards.

Signed-off-by: Zadak <rarepops@protonmail.com>
2026-07-04 16:47:06 +02:00
Zadak dc8c5be587 fix(graph-ui): clarify graph HUD labels and empty state
Rename the ambiguous Clear button to Clear selection so it reads distinctly from Refresh, and point the empty-state hint at the Projects tab instead of the renamed Stats tab.

Signed-off-by: Zadak <rarepops@protonmail.com>
2026-07-04 16:47:06 +02:00
Zadak 0c7883eb2f feat(graph-ui): sync active tab and project to the URL
Store the active tab and selected project in the URL query string so the view survives refreshes and can be bookmarked or shared, syncing back on browser back and forward via popstate.

Query params are used rather than path segments because the embedded server only serves index.html at the root path. Opening the Projects tab now clears the active project, and the Graph tab stays disabled until a project is selected.

Signed-off-by: Zadak <rarepops@protonmail.com>
2026-07-04 16:47:06 +02:00
Zadak 8ea42911a4 feat(graph-ui): scrollable filter panel with clearer grouping
Cap the filter panel height and move the node and edge chips into a scroll area so a long type list no longer pushes the folder tree off screen.

Regroup the chips under Node types and Relationships, pin the show-labels toggle as a footer, and add a Folders heading (en and zh) above the folder tree so the section is clearly labelled.

Signed-off-by: Zadak <rarepops@protonmail.com>
2026-07-04 16:47:06 +02:00
Martin Vogel c7374135ac Merge pull request #847 from DeusData/distill/ui-label-and-ts-repro
fix(graph-ui): honest Project-ID label; RED repro for TS inherited-method gap
2026-07-04 15:31:27 +02:00
Martin Vogel b56cb592e9 fix(graph-ui): label Project name honestly as permanent ID
The New Index modal presented the project name as an optional display
name, but the value becomes permanent identity: the database filename,
the projects primary key, and the QN prefix of every node. It can never
be renamed afterwards.

Implements the decision from the #805 review: keep the field, make it
honest. The label now reads "Project ID (optional — permanent, cannot
be renamed)", the placeholder and a new help line explain that the ID
becomes the database name and query prefix and that leaving it blank
derives it from the path. Both EN and zh locales updated, test asserting
the old label adjusted.

Co-authored-by: rarepops <rarepops@protonmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-04 11:35:41 +02:00
sahil-mangla 00b2a0e044 test(graph-ui): fix locale-dependent number-format assertion in GraphTab.test.ts
Pin formatGraphLimitNotice to 'en-US' to resolve unit test failures under non-en_US (e.g. German de_*) locales.

Fixes #825

Signed-off-by: sahil-mangla <manglasahil2017@gmail.com>
2026-07-04 14:32:29 +05:30
Zadak d72a84ff40 fix(graph-ui): scope typed-path picker refresh to Windows drive paths
The debounced folder-list refresh (and Enter-to-navigate) on the Repository path field now only fires for Windows drive paths (e.g. 'D:/'), where typing is the mechanism for switching drives. POSIX path navigation is left exactly as before.

Add a regression test asserting a typed POSIX path does not trigger a re-browse.

Signed-off-by: Zadak <rarepops@protonmail.com>
2026-07-04 09:39:28 +02:00
Zadak b15dff53a4 fix(graph-ui): drop the empty '/' root on Windows, offer the drive instead
On Windows the POSIX '/' quick-jump root is meaningless (browsing it returns an empty listing), yet the picker showed a '/' button whenever the backend did not enumerate drives (older builds return roots=['/']). Clicking it stranded the user on an empty view.

Derive Windows-aware quick-jump roots: drop non-drive roots and always include the current drive (parsed from the browsed path), so the button lists the drive root. Other drives remain reachable by typing. Add a regression test.

Signed-off-by: Zadak <rarepops@protonmail.com>
2026-07-04 09:39:28 +02:00
Zadak f50bf902aa fix(graph-ui): refresh folder list when a path is typed in the picker
Typing a path into the Repository path field updated currentPath (and thus the breadcrumb) but never re-fetched the directory listing, so switching drives by typing (e.g. 'D:/') left the previous location's folders showing. This is the only way to change drives when the backend does not enumerate drive roots.

Debounce a silent /api/browse refresh when the typed path changes (keeping the last good listing instead of flashing errors mid-typing), and navigate immediately on Enter. Add a regression test.

Signed-off-by: Zadak <rarepops@protonmail.com>
2026-07-04 09:39:28 +02:00
Zadak 4efa22cd95 fix(graph-ui): navigate Windows drive breadcrumbs to real paths
The index file picker built breadcrumb targets as '/' + segments, so on a Windows drive path (C:/Users/rap) clicking a crumb browsed to '/C:/...', which the backend rejected as 'not a directory'. Only the '.. (up)' button worked.

Build drive-aware crumb targets (C:/, C:/Users) and drop the bogus unified '/' root crumb on Windows drive paths; POSIX behavior is unchanged. Add a regression test for Windows breadcrumb navigation and cleanup() for test isolation.

Signed-off-by: Zadak <rarepops@protonmail.com>
2026-07-04 09:39:28 +02:00
Martin Vogel cfc73f8d0b fix(graph-ui): surface indexing-job failures instead of silently completing
The backend /api/index-status reports status:"error" plus an error
message for failed indexing jobs, but IndexProgress treated any
non-"indexing" state as successful completion: the spinner vanished
with no feedback (e.g. after an OOM-killed indexer subprocess), the
project never appeared, and no error was shown.

Render a visible error banner (path + error text) with a Dismiss
button instead, and keep the success flow unchanged. Beyond the
original PR:

- Restore the empty-jobs guard the PR dropped: the backend keeps
  finished jobs listed as "done"/"error" (handle_index_status only
  skips idle slots), so an empty list mid-index only occurs on
  transient state loss and must not be treated as completion.
- Route the new user-facing strings through the i18n system
  (projects.indexingFailed, common.dismiss; EN + zh entries).
- Cover error banner + dismiss, success flow and the empty-jobs
  guard with vitest cases on the now-exported IndexProgress.

Distilled from #549.

Refs #524

Co-authored-by: sahil-mangla <manglasahil2017@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-03 20:28:27 +02:00
Zadak a0e970d78d fix(graph-ui): keep filter sidebar visible when all nodes are filtered out
Clicking "None" (disable all filters) clears every label and edge type,
making filteredData.nodes empty. The early return then replaced the whole
layout — including the left filter sidebar — with a centered "All nodes
filtered out / Reset Filters" message, leaving no way to re-enable
individual filters without resetting everything.

Narrow the early return to the genuinely-empty / no-data case and render
the "all filtered out" state inside the graph area instead, so the
FilterPanel and Sidebar stay mounted. As a side effect, a project with
zero nodes now correctly reads "No nodes in this project" rather than
"All nodes filtered out".

Add a regression test that disables all filters via "None" and asserts the
Filters sidebar remains in the document.

Signed-off-by: Zadak <rarepops@protonmail.com>
2026-06-30 21:27:41 +02:00
Martin Vogel d5414111b3 feat(ui): add i18n and index picker UX
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-06-28 21:21:32 +02:00
Martin Vogel d9d70656b4 fix(ui): cap graph rendering and index spawn
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-06-28 20:57:14 +02:00
Martin Vogel a3336bf346 chore(security): resolve flagged Scorecard + Dependabot alerts
- graph-ui: bump vite ^6.4.2 -> ^6.4.3 and add overrides pinning the transitive
  form-data >=4.0.6 and @babel/core >=7.29.6 (all dev-scope build/test deps, not
  shipped in the binary). Clears the 4 open Dependabot alerts and Scorecard's
  VulnerabilitiesID. `npm audit` now reports 0 vulnerabilities.
- codeql.yml: move `security-events: write` from the workflow top level to the
  `analyze` job (top level is now `contents: read`), resolving Scorecard's
  TokenPermissionsID least-privilege finding. CodeQL still uploads results from the
  job-scoped token.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-06-24 00:34:05 +02:00
Nan 550a2cc65c fix(ui): render graph labels without remote font fetches (#496)
Signed-off-by: Nan <630971265@qq.com>
2026-06-22 22:04:54 +02:00
Martin Vogel 952eea9f1f Extend the license gate to ancestry coverage and the UI dependency tree
Structural layer: drop the hardcoded component list — any directory
containing vendored source or data files must now be covered by a
license file in itself or an ancestor within the vendored tree, so
newly vendored code without a license fails immediately. New third
layer: the graph-UI npm production tree is resolved (postinstall
scripts disabled) and every bundled package must be on the policy
allow-list; unknown licenses fail. Reclassifies @tailwindcss/vite as a
devDependency — it is a build plugin and was inflating the production
tree with native tooling.
2026-06-12 13:03:12 +02:00
Martin Vogel 64be28083e fix(deps): bump vitest 3 -> 4.1.8 (GHSA-5xrq-8626-4rwp)
Resolves the critical Dependabot/Scorecard alert for vitest <4.1.0 (UI server
arbitrary file read/exec). graph-ui has no test files, so the major bump is
risk-free here (CI uses 'vitest run'); lockfile regenerated, vitest 4.1.8.
2026-06-01 22:57:54 +02:00
Martin Vogel d1b676fc0d fix(deps): bump ws to 8.21.0 (CVE-2026-45736, GHSA-58qx-3vcg-4xpx)
ws 8.19.0 had a moderate uninitialized-memory-disclosure issue in websocket.close() (fixed in 8.20.1). Transitive dev dependency; the existing ^8.18.0 range already permits the fix, so this is a lockfile-only bump. npm audit now reports 0 vulnerabilities.
2026-05-29 00:36:24 +02:00
Martin Vogel 7290c1fd2b feat(ui): satellite galaxy radius spacing + cross-galaxy edge rendering
Two visualization gaps in the cross-repo graph view:

1. Fixed LAYOUT_GALAXY_SPACING=600 buried satellite galaxies inside the
   primary cluster on any non-trivial project — a 1000-node primary
   cluster has bounding radius ~1500. Added layout_radius() (max distance
   from origin across all node positions) and compute the per-satellite
   distance as primary_radius + sat_radius + LAYOUT_GALAXY_PAD, with the
   old constant retained as a lower bound for tiny projects.

2. cross_edges was hard-coded to an empty array (TODO placeholder), so
   inter-galaxy CROSS_* edges never rendered even when the matcher had
   produced them. Populate from a join on the source store's CROSS_*
   edges → Route node qualified_name, then resolve the canonical QN in
   the linked store to get its node id. The Route QN is the cross-repo
   matching contract; properties.target_function isn't unique.

Required keeping both `store` and `lp_store` open through the linked-
projects loop instead of closing them right after layout compute.
Added cbm_store_close to all early-error paths to balance the lifetime.

Frontend side:
- EdgeLines accepts an optional targetNodes prop so a single component
  can render edges with source in one node array and target in another
  (offset-adjusted satellite nodes).
- GraphScene renders an EdgeLines layer per linked project for its
  cross_edges, using primary nodes as source and offsetNodes as target.
- GraphTab folds linked-project labels and edge types into the filter
  init / enableAll / filteredData paths so cross_edges respect the
  current filter state.
- Edge color palette extended with GRPC/GRAPHQL/TRPC/CROSS_* tones.

Co-authored-by: sponger94 <45746997+sponger94@users.noreply.github.com>
2026-05-10 18:56:30 +02:00
Martin Vogel 4fdcdd4239 fix(security): close open scanner alerts
Dependabot/CodeQL #33 — postcss XSS GHSA-qx2v-qp2m-jg93:
  npm audit fix in graph-ui (8.5.8 -> 8.5.14, above the <8.5.10 vuln range)

CodeQL #39 — TOCTOU race in artifact.c ensure_gitattributes():
  Replace stat() + fopen() with open(O_WRONLY|O_CREAT|O_EXCL).
  Atomic create-only-if-absent closes the check-vs-write window.
  Falls through to merge driver setup if file already exists.

CodeQL #55 — pip install not pinned in release.yml:
  Pin build==1.3.0 and twine==6.2.0. Comment explains why
  --require-hashes is not used (transitive-deps overhead).

Dismissed (won't-fix):
  - #56 contents: write — required for 'gh release edit --draft=false';
    no narrower permission exists.
  - #54-51 Crystal grammar warnings — vendored upstream code.
  - #50-40 Agda grammar warnings — vendored upstream code.
2026-05-05 00:51:52 +02:00
test b6eefe014c Add multi-galaxy UI layout and cross-repo architecture summary
Backend (http_server.c):
- Layout API detects CROSS_* edges and loads linked project graphs
- Each linked project computed at radial offset as satellite galaxy
- Response includes linked_projects with nodes, edges, offset

Frontend:
- LinkedProject type in types.ts
- GraphScene renders satellite galaxies at offset positions
- NodeCloud/EdgeLines accept opacity prop for dimmed rendering

Tool integration (mcp.c):
- get_architecture includes cross_repo_links summary
- Extract append_cross_repo_summary helper for linter compliance
2026-04-15 23:53:42 +02:00
DeusData e21eba6348 Bump vite to 6.4.2 to fix CVE GHSA-4w7w-66w2-5vf9 and GHSA-p9ff-h696-f583
Fixes arbitrary file read via dev server WebSocket and path traversal
in optimized deps .map handling.
2026-04-12 19:06:28 +01:00
Martin Vogel 1ad2568d66 Fix smoke 8o: OpenCode command array + update picomatch (dependabot) 2026-03-25 23:08:02 +01:00
Martin Vogel 18fa9979ee Replace vmem with mimalloc global allocator, add extraction-phase prescan, fix __init__.py QN collision
Memory management:
- Vendor mimalloc v2.1.9 as global allocator (MI_OVERRIDE=1 in prod)
- New mem.h/mem.c: RSS-based budget tracking via mi_process_info()
- Remove vmem.c/vmem.h (mmap-based budget tracking)
- Remove slab tier2 bump allocator (~300 LOC); >64B goes to mimalloc
- Slab tier1 pages from malloc (= mimalloc) instead of vmem
- Arena blocks from malloc instead of vmem
- Budget raised from 35% to 50% RAM (no more untracked C++ heap)

Extraction-phase prescan (eliminates disk re-reads):
- HTTP call sites: keyword check + URL extraction during extraction
- HTTP routes: decorator + source-based extraction during extraction
- Config file refs: regex scan during extraction
- httplinks: 41.8s → 13ms on Linux kernel (3,212x faster)
- configlink: 41.4s → 0.8s on Linux kernel (54x faster)
- Linux kernel fast-mode total: 2m38s → 1m18s

Bug fixes:
- __init__.py Module QN no longer collides with Folder QN
- index.ts same fix for JS/TS packages
- 13 regression tests for QN collision at FQN + extraction layers
- search_graph/search_code default limit raised from 10 to 500k
- Resolve all clang-tidy, cppcheck, and clang-format warnings

Repo cleanup:
- tree-sitter-form, tree-sitter-magma moved to tools/
- .gitignore: build/, node_modules/, graph-ui/dist/, TEST_PLAN.md
2026-03-17 22:05:57 +01:00