feat: online verification, node removals, and knowledge browser
CI / quality (push) Canceled after 0s
CI / quality (push) Canceled after 0s
Adds claim-bound online evidence records and citation verification scripts, node removal transport/adapter, retrieval benchmark and owner-review tooling, GraphCanvas/SourceDetail knowledge browser UI, and the ingest handoff helper. Bumps the agents/gewu pack to the online-verification branch.
This commit is contained in:
1 parent
a3156f4e08
commit
78033bb830
83 files changed
+13476
-1051
No files matched your search
@@ -0,0 +1,142 @@
|
||||
# UI usability and performance review
|
||||
|
||||
Reviewed 16 October 2026. Status: audit and proposed implementation plan; application fixes have not been applied in this review.
|
||||
|
||||
The first priorities are dependable automatic loading and graph rendering. The current problems include demonstrated functional defects, CSS regressions, and server work that grows with the entire collection. A responsive stylesheet alone will not resolve them. Build source detail navigation and compact filters on top of a shared, cancellable read layer.
|
||||
|
||||
This review extends the [knowledge browser plan](../KNOWLEDGE_BROWSER_PLAN.md). Its measured findings qualify the earlier [browser acceptance note](../acceptance/2026-10-16-knowledge-browser.md): descriptions of prominent graph edges, grouping, shared-source links, and completed visual behavior should not be treated as current acceptance evidence.
|
||||
|
||||
## Evidence and limits
|
||||
|
||||
Baseline commit: `a3156f4e08fac5b34739f82b0c72b28dbcf11349`. Reviewed the existing working tree, which already contained extensive staged and unstaged changes. No application code, canonical knowledge, credentials, or running service configuration was changed. Build and test commands generated their usual ignored outputs.
|
||||
|
||||
Inspected both supplied screenshots, frontend code and styles, browse/graph/source reads, and the running Vite frontend through T3 collaborative preview at the existing HTTPS service address. The preview started without an authenticated session. Browser interactions used synthetic API responses in that page's memory: three nodes, one source, long titles/project name, and 100 tag values. They did not test the real authenticated backend. Browser-memory mocking was injected through a page script; the synthetic credential was never a valid service credential.
|
||||
|
||||
Preview screenshot, click, and type operations repeatedly failed. DOM evaluation worked and was used to drive the fixture. Resize settings also diverged from the measured viewport. Therefore the eight-size matrix below uses isolated same-browser iframe copies of the rendered application DOM and loaded CSS, with each iframe's viewport measured. This verifies static responsive geometry, not eight independently exercised application sessions. [Raw measurements](../acceptance/2026-10-16-ui-audit-measurements.json) retain the dimensions and computed graph styles. No screenshot or full visual acceptance is claimed.
|
||||
|
||||
Real API p50/p95 latency, intermittent production graph failures, Safari/iOS, physical touch, screen readers, and 200% zoom remain unverified. The reported “half the time” failure rate is user-reported, not a measured rate from this audit.
|
||||
|
||||
## Findings
|
||||
|
||||
| Priority | Finding and consequence | Evidence / owning code |
|
||||
| --- | --- | --- |
|
||||
| P0 | Graph relationships can be returned successfully but remain invisible. | Computed edge `stroke: none`; nodes have black fill. `App.tsx` emits `.graph-link` and `.graph-node--active`, while styles expect `.graph-link--accepted`, type-specific node classes, and `.graph-node--selected`. |
|
||||
| P0 | Sources requires an explicit load action. An unopened tab looks empty despite a positive project source count. | Opening Sources issued no `/v1/sources` request; pressing Load source records issued it. `loadSources` has no automatic-load effect or prefetch caller. |
|
||||
| P1 | No navigable source detail page or complete source-to-knowledge view exists. | Source titles render as plain `h3` elements. The fixture row offered only an archive download. `openSource` depends on a selected node and is an inline reader feature. |
|
||||
| P1 | Graph geometry wastes space on phones and can overlap following content on desktops. | Minimum viewBox is 1000×640; coordinates are shifted toward the top-left. `.graph-canvas` styles the wrapper, while its SVG has no explicit fitting dimensions. See size matrix. |
|
||||
| P1 | Filter choices stack their checkbox, text, and count vertically. | `.facet-filters label` overrides the earlier `.facet-choice__options label` flex display with an equally specific, later grid rule. A short tag occupies about 58 px vertically. |
|
||||
| P1 | Search and filters use inconsistent apply rules. | Query typing waits for submit; tag/audience/use/domain/type changes submit immediately; confidence and dates wait. The existing plan calls for debounced search and immediate filter application. |
|
||||
| P1 | Data loading has no reusable query cache or independent request ownership in the reader. | `main.tsx` installs TanStack Query, but `App.tsx` uses manual fetch/state. Sources shares `requestGeneration` with search/node reads and does not advance its own generation per request. Graph generation advances on graph loads, not every scope invalidation. These are race risks; not all were reproduced. |
|
||||
| P1 | Secondary reader failures can prevent the main record from opening. | `openNode` awaits details, history, and neighbors in one `Promise.all`; any rejection clears the selected record. A direct link may fetch details once to resolve the route and again in `openNode`. |
|
||||
| P1 | Browsing performs collection-sized work for a 20-row page. | `SearchService.browse` loads up to 100,000 project nodes and up to 100,000 filtered nodes, adds a shared scan when requested, sorts in Python, and builds facets by iteration. Source counts are separate reads. This is a confirmed scaling issue, not a measured explanation of current real API latency. |
|
||||
| P1 | A cold/stale derived index can make a read expensive. | `SQLiteSearchIndex._rebuild_if_needed` loads canonical nodes, edges, and sources and rebuilds synchronously when its revision is stale. Measure this separately from warm reads. |
|
||||
| P2 | Graph controls promise more than their behavior delivers. | Group by type sorts node input rather than creating labeled spatial groups. Fit to selection fetches a neighborhood; it does not fit a camera. Zoom CSS-scales the whole SVG. There is no pan interaction or actual type legend. Shared-source links described by the earlier note are not generated by this renderer. |
|
||||
| P2 | Navigation, loading status, and density need consolidation. | Primary tabs appear in both rail and project navigation. The Sources status persisted into Graph. Scope copy can say shared knowledge is included while the current browse scope excludes it. Raw revision IDs and timestamps compete with titles. |
|
||||
|
||||
### Responsive results
|
||||
|
||||
Expanded Knowledge filters, three graph nodes, and the loaded source list were checked at every listed size. Numbers are approximate CSS pixels; classic scrollbars explain most client-width differences. At 320 px, content reaches the nominal viewport edge despite a narrower scrollbar-adjusted client area, so that case requires another reflow check.
|
||||
|
||||
| Viewport | Expanded filter height | Graph label height | Layout result |
|
||||
| --- | ---: | ---: | --- |
|
||||
| 320×800 | 1,109 | 3.4 | Filter/search section about 1,546 px tall; graph mostly unreadable. Source actions stack. |
|
||||
| 390×844 | 1,109 | 4.2 | Same excessive filter height; graph wrapper about 608 px, actual SVG only 222 px. |
|
||||
| 768×1024 | 597 | 9.3 | Two-column facets still consume much of the screen. Graph labels remain small. |
|
||||
| 1024×768 | 416 | 9.3 | Four facet columns squeezed beside the 188 px rail; search section about 728 px tall. |
|
||||
| 1440×900 | 384 | 13.5 | SVG about 707 px high inside a 648 px wrapper, extending into following content. |
|
||||
| 1920×1080 | 384 | 18.5 | SVG about 950 px high inside a 760 px wrapper. |
|
||||
| 2560×1440 | 384 | 18.5 | Workspace remains 1,580 px wide, anchored after the rail; large unused right side. |
|
||||
| 3440×1440 | 384 | 18.5 | Same cap and placement; graph gets no additional exploration space. |
|
||||
|
||||
The long-title source fixture wraps and its actions stack below 820 px. That is useful existing behavior to preserve, but does not establish touch usability or source-page acceptance.
|
||||
|
||||
### Performance observations
|
||||
|
||||
`npm --prefix web run build` passes. The emitted main JS is 383.28 kB, 114.92 kB gzip; CSS is 33.31 kB, 6.81 kB gzip. Bundle size is worth tracking, but no evidence establishes it as the main delay. Admin and reader currently ship in the same entry; route splitting is a later improvement after data-loading fixes.
|
||||
|
||||
A warm, same-realm Node microbenchmark of the current `graphLayout` function used five warm-ups and 25 recorded runs per size:
|
||||
|
||||
| Nodes / edges | Median | Sample p95 |
|
||||
| --- | ---: | ---: |
|
||||
| 3 / 6 | 0.13 ms | 0.80 ms |
|
||||
| 51 / 102 | 3.01 ms | 22.53 ms |
|
||||
| 200 / 400 | 17.31 ms | 18.68 ms |
|
||||
|
||||
This excludes network, SVG rendering, React, and mobile CPU cost. It is not an end-to-end benchmark. An initial cross-realm VM experiment was discarded because VM overhead distorted results. The layout does 90 all-pairs passes plus edge endpoint searches; it runs synchronously inside `useMemo`. Do not attribute the tiny three-node screenshot problem to force-layout CPU cost. Fix geometry and CSS first; profile the capped case in-browser before choosing a worker or library.
|
||||
|
||||
## Proposed experience
|
||||
|
||||
### Automatic loading and background refresh
|
||||
|
||||
Opening a project loads its first knowledge page automatically. Sources loads automatically when opened and prefetches its first page after the primary view settles. Prefetch graph data on tab intent/focus or idle when appropriate; defer layout until Graph is visible. Prefetch only bounded first pages, never the entire library or all archives.
|
||||
|
||||
Use the already installed TanStack Query through a typed read client. Query identity includes authenticated identity, project/shared scope, normalized filters, cursor, and node/source revision. Keep successful content visible during refresh with a small refreshing indicator. Use independently loading panels for article, relationships, and history; history can fetch on disclosure. A neighbor error must not discard the article. Cancel superseded fetches with the supplied signal, reject obsolete responses, and clear private caches on logout/identity change. Start with a configurable 30-second freshness window and refresh on reconnect/tab return when stale; tune against ingestion frequency. No service worker or persistent private cache is needed for this change.
|
||||
|
||||
This follows the installed library's supported [prefetching](https://tanstack.com/query/latest/docs/framework/react/guides/prefetching) and [cancellation](https://tanstack.com/query/latest/docs/framework/react/guides/query-cancellation) patterns. Verify exact APIs against the locked version during implementation rather than copying a newer documentation example blindly.
|
||||
|
||||
### Compact, composable filters
|
||||
|
||||
Default toolbar: search, type, sort, and **Add filter**. Selecting Add filter offers Tag, Audience, Use, Domain, Confidence, Knowledge date, and Past freshness date. Render only added filters, for example `Tag: narrative ×` and `Audience: developers ×`. Each chip reopens its editor; Clear all is secondary. Keep project/shared scope consistently available across tabs.
|
||||
|
||||
Each label editor opens on click, keyboard focus/activation, or typing. Typeahead narrows project-wide known labels, displays counts and checked selections, and keeps selected chips visible. Double-click may also open it, but must never be required on touch. Use a bounded searchable multi-select popover on desktop and a focused sheet on phones. Support Arrow keys, Enter, Escape, removal, and focus restoration following the [WAI combobox pattern](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/). Explicitly handle zero suggestions and loading/error states. Do not silently create taxonomy labels from search text.
|
||||
|
||||
Apply discrete selections immediately; debounce free text about 250–300 ms. Date ranges commit once valid. On phones, an explicit Done action may close the sheet; it must not introduce a second hidden apply step. Preserve OR within a facet and AND across facets, with brief help available on demand. Reset the cursor when criteria change; retain applied filter state in the URL. Start with local filtering for the current 100-value catalog; add scoped prefix/pagination lookup for larger catalogs rather than mounting every option. Virtualize only if measured necessary.
|
||||
|
||||
### Dedicated source pages
|
||||
|
||||
Make each source title a real link. Proposed route: `/projects/:project/sources/:revisionId`, encoding IDs as path segments and preserving existing node/query URLs. The page contains title, publisher/type, capture date, availability, original/archive actions, and **Knowledge using this source** with total, search/type filtering, and cursor pagination. Keep raw IDs and extraction metadata under Details. A source with zero related accepted nodes is a valid page.
|
||||
|
||||
Define related knowledge explicitly: accepted nodes citing the selected source revision, plus accepted explicit node-to-source relationships shown with their relationship basis. Do not equate text mentioning a source with evidence. Distinguish revision-specific results from an optional all-revisions view; deduplicate node identities and paginate the union.
|
||||
|
||||
Add a scoped reverse-reference read, provisionally `GET /v1/projects/{project}/sources/{revision}/nodes`, returning bounded node summaries, relation basis, total, cursor, and index revision. The existing source metadata/artifact APIs can be reused. Build a derived citation-to-node index from canonical evidence references and combine it with explicit edges; it must be rebuildable. Apply source grants, node project authorization, visibility/removals, and any shared inclusion before totals and rows. Hidden nodes must not leak through counts. Do not download all nodes and reverse-filter them in the browser.
|
||||
|
||||
Opening a related node and returning must restore source, filters, page, and scroll. Use the same source route from citation links, graph source targets, and the Sources list. Source metadata and related-node reads fail independently. Historical node links keep their existing revision semantics.
|
||||
|
||||
### Graph and responsive shell
|
||||
|
||||
First restore the renderer/style contract: explicit node colors, visible directed edges, selected state, toolbar styling, and SVG width/height constrained to its container. Measure the container with `ResizeObserver`. Fit actual node-and-label bounds with padding; handle one node, disconnected components, long titles, and collisions. Avoid scaling all labels down to fit a fixed oversized coordinate space.
|
||||
|
||||
Separate **Fit view**, **Center selected**, and **Explore neighbors**. Zoom/pan should affect graph coordinates within a clipped viewport. Keep selection preview and a usable accessible list; do not require dragging to read nodes. Show selected labels reliably; reduce unselected label clutter at scale. Either implement real spatial grouping with named groups or remove the grouping control. Explain truncation and show errors/retry within Graph. Initially draw accepted relationships only; add shared-source links only with an explicit derived-data contract and legend.
|
||||
|
||||
Phones use one column, compact project context, large controls, a filter sheet, and a collapsible graph/list arrangement with readable node details below or in a sheet. Tablets can use a collapsible rail and optional detail pane when width permits. Desktops use list plus detail when useful. Extra-wide screens give graph/list exploration more room while keeping prose at a readable measure; center bounded reading content. Remove duplicate primary navigation and avoid making a generic hero consume the first screen. Preferred interactive target is 44×44 CSS px. Check keyboard, reduced motion, 200% zoom, long multilingual labels, and local rather than page-wide scrolling.
|
||||
|
||||
## Implementation sequence and acceptance
|
||||
|
||||
| Slice | Owning modules and delivery | Acceptance gate |
|
||||
| --- | --- | --- |
|
||||
| 1 — Loading and graph correctness | Typed read client/query hooks; `App.tsx`; graph component/styles. Auto-load Sources, independent loading/error states, cancellation, graph CSS/geometry repair. Add request/status timing instrumentation. | Sources shows data without a load button; repeat navigation uses cache; a failing history/neighbor request leaves the node readable. Visible graph edges and usable labels at phone/desktop widths. Rapid project/tab changes never show old-scope data. |
|
||||
| 2 — Bounded server browsing | Search application/SQLite adapter. Move filtering/order/paging into SQL; use scoped count/facet aggregates and a coherent revision. Separate cold rebuild timing and evaluate generation-based background rebuild publication. | 20-row responses do not deserialize every matching node. Totals/facets and cursors remain correct under removals, shared scope, revision changes, and denied projects. Benchmark warm and cold paths separately. |
|
||||
| 3 — Filters and responsive shell | `KnowledgeFilters`, label picker, project shell; retain existing tokens. Replace all-at-once facets with added chips, consistent apply rules, URL state, responsive navigation. | 100 and 1,000-label fixtures remain searchable; all operations work with keyboard and touch-sized controls. Results stay reachable on 320/390 px screens. Back/reload restores filters; no page overflow at 200% zoom. |
|
||||
| 4 — Source detail vertical slice | Source reverse-reference adapter/application/HTTP contract, generated types, `SourcePage`, links from list/evidence. | Direct/reloaded source URLs work. More than 40 related nodes paginate correctly; revision distinctions and relation basis are clear. Denied nodes contribute neither rows nor totals. Return restores source list position. |
|
||||
| 5 — Graph exploration and release verification | `KnowledgeGraph`, layout/camera boundary, responsive preview/list. Profile before adding a dependency or worker. Update acceptance evidence. | One/isolated/disconnected nodes and 200 nodes/500 edges; real fit, pan, zoom and selection; truncation/error/retry; readable phone labels; no SVG overlap. Full navigation and visual matrix below passes. |
|
||||
|
||||
Keep feature extraction tied to these behaviors: `KnowledgePage`, `KnowledgeFilters`, `SourcesPage`, `SourcePage`, `KnowledgeGraph`, and query hooks. Preserve the current editorial design and read-only knowledge boundary. Avoid a wholesale framework rewrite. Make each slice independently reviewable and update the existing plan/acceptance note when its behavior is actually delivered.
|
||||
|
||||
### Performance targets to validate, not current results
|
||||
|
||||
Use a repeatable fixture with at least 1,000 nodes, 100 sources, 1,000 labels, and a capped 200-node/500-edge graph. Also test 40+ related nodes and duplicate sort titles. Record machine, browser, API revision, network profile, corpus size, cache state, p50/p95, errors, and long tasks. Repeat a constrained-mobile CPU/network profile and real-device checks.
|
||||
|
||||
- Warm browse/source read p95 below 300 ms on the documented local benchmark host.
|
||||
- First useful content within 2 seconds on the agreed constrained-network profile; define that profile in the implementation receipt.
|
||||
- Cached tab switches show content within 100 ms; background refresh never blanks the current view.
|
||||
- Input and selection feedback below 100 ms; no graph-layout main-thread task exceeding 50 ms on the target mobile profile. Use a worker or bounded incremental layout if profiling requires it.
|
||||
- No unbounded prefetch or collection-sized per-page deserialization. Bound responses, cache, retries, and layout work.
|
||||
- Exercise ingestion/index changes while reading: coherent generations, bounded refresh, recoverable expired cursors, no retry loop.
|
||||
|
||||
For intermittent graph failures, correlate a visible request ID with HTTP status, response duration, returned node/edge count, index revision changes, layout duration, and browser exceptions. Test timeout, 401, 403, 409, 500, and disconnected network deliberately. The invisible-edge bug explains one successful-response failure mode; it does not establish the cause of every reported failed load.
|
||||
|
||||
## Checks run and remaining release gates
|
||||
|
||||
| Check | Result |
|
||||
| --- | --- |
|
||||
| `npm --prefix web run build` | PASS: TypeScript and Vite production build. |
|
||||
| `npm --prefix web exec -- eslint .` (from `web/`) | PASS. |
|
||||
| `npm --prefix web run test` | FAIL: 18 passed, 6 failed across 24 tests; App reader tests passed, failures in Accounts/Admin/ProjectsSection. Several assertions still expect old headings or inline form copy. Existing failures were recorded, not repaired by this audit. |
|
||||
| `npm --prefix web run test:e2e` | FAIL: both existing Chromium tests. Fixtures intercept `/v1/search`, whereas the current reader calls `/v1/knowledge/browse`; assertions also expect old button-based result navigation. Tests must follow the current contract before they provide useful release evidence. |
|
||||
| Synthetic runtime Sources/Graph flow | Reproduced manual Sources load, absent source detail link, invisible graph edge, black nodes, small labels, and stale cross-tab status. |
|
||||
| Eight-size Knowledge/Graph/Sources geometry | Completed static DOM/CSS measurements; limitations described above. |
|
||||
| Saved screenshots / visual regression | BLOCKED by preview snapshot failure. Supplied screenshots support the initial visual findings. |
|
||||
| Real authenticated API latency / intermittent graph rate | NOT RUN; no authenticated production benchmark was performed. |
|
||||
| Safari/iOS, Android touch, keyboard-only complete journey, screen reader, 200% zoom | NOT RUN; required before claiming full responsive/accessibility support. |
|
||||
|
||||
Add browser tests for automatic background loading, cache reuse, aborted/stale responses, compact filter keyboard behavior, source-to-node navigation, source revision paging, graph styling/camera behavior, and responsive layout. Repair existing fixture drift before relying on the suite. Pair DOM assertions with rendered screenshots and manual reading tasks once preview capture works. A passing build or a zero-overflow assertion alone is insufficient evidence of a usable interface.
|
||||
Reference in new issue
Block a user