Making it legible: conflicts you can act on, and knowing your own machine
June 8, 2026Build LogUXKnowledge Graph
A long UX sweep, driven almost entirely by someone sitting next to me poking at the app and asking the questions a real user asks. Every one of those questions turned into a feature.
The last few days were a long UX sweep, and almost every feature came from the same place: someone sitting next to me, poking at the app, asking the questions a real user asks. "Why is it confusing?" "Where are the files?" "Which rule do I follow?" "Why is it down?"
Every one of those became a feature. Yun ang pinakamagandang backlog (that's the best kind of backlog).
Business rules you could read but not use
The rules view was technically correct and practically useless — a flat wall of roughly 2,773 near-identical cards, most of them low-signal test candidates. "Show the business rules in a better way" was the ask, and it was right.
Now rules are grouped by their source file, collapsible, with conflicted sources floated to the top. Each group header carries a count, a status breakdown, and conflict / overlap badges. Two new filter chips — conflicts-only, and tests (hidden by default now, because they were the noise). The interesting groups auto-expand.
The wall became a tree you can navigate. Same data. Completely different tool.
"Conflicts with what?"
The next question wrote itself. A rule says it has 5 conflicts — with what?
So clicking a flagged rule now expands a panel listing every rule it clashes with, and the source file each one came from. "This rule has 5 conflicts" became "this rule conflicts with underwriting-policy-2025.md."
And that immediately raised the question I'm happiest about, because it's the right one:
"So how does the user decide which one to follow, and do we create a document so we have an audit?"
That is the entire governance thesis in one sentence. AI raises, human decides, everything audited. It's now the next big build.
"Where are the files?"
You could double-click a source in the sidebar and nothing happened — it just quietly filtered the chat. And for someone who didn't ingest the data, the sidebar counts are a mystery. "If I'm a new user, I don't know any of these."
So: a proper explorer. Every ingested file, commit, and local document — filterable, with a master-detail preview showing the full text, reconstructed from chunks when it wasn't stored inline. You can finally see what's in the box.
Knowing your own machine
A system information panel that does best-effort hardware introspection, reports which model providers are configured and actually reachable, and recommends models sized to the detected hardware.
It also quietly answered an earlier scare. The status pill had flashed "Down" once, and the question was why. It wasn't down — a background ingest had saturated the single-worker backend, and one five-second health poll timed out. Now datastore health, providers, and hardware all live on one screen. "Why down" has a place to look instead of a guess.
What I learned
The value isn't the feature. It's making the feature legible. A conflict you can't trace. Files you can't see. A status you can't explain. A machine you're guessing about. Every one of those was "working" and useless.
And the best feature ideas didn't come from a roadmap. They came from watching someone not understand the screen.
The endpoints, the war stories, and the plan
The endpoints, the code, and the specific tooling. Same password as the other Knowledge Platform posts — or request access below.
The rules grouping, concretely
Backing the badges: a cheap GET /graph/rule-conflict-counts that returns per-rule conflict and overlap degree straight from the graph database. The badges cost one query instead of N.
For the "conflicts with what" panel, the /graph/rule-relations endpoint already existed for the graph's click panel. I taught it to also return the other rule's source document via an OPTIONAL MATCH (b)-[:DERIVED_FROM]->(:Document). That one clause is what turns "5 conflicts" into named files.
The explorer
New GET /documents and GET /documents/{id}. The detail view reconstructs a document's full text from its chunks when the original wasn't stored inline — which matters because the chunking is lossy by design and you want the human reading the real thing.
Browse anywhere, opt-in
The file picker was sandboxed to the project root — a deliberate path-traversal guard. But the platform is local-first and single-user, so I made it opt-in openable via an environment flag. Now the picker can reach any drive or folder, so you can ingest a repo that lives elsewhere. The Windows drive list comes from os.listdrives().
Default stays sandboxed for anyone who clones. The unsafe mode is a deliberate choice a person has to make, not a default they inherit.
A browsable graph filter
The graph filter was a blind text box. Now GET /graph/facets feeds a <datalist> of the real files, commits, and documents, so you pick instead of guess. Plus a fresh-graph mode: select folders, wipe the graph, rebuild from only that selection. Good for a focused demo of one domain.
The system panel
GET /system/info does best-effort hardware introspection with Windows-aware probes, reports configured and reachable providers, the embedding stack, and model recommendations sized to detected hardware. On my box it correctly reads the GPU, VRAM, system RAM, and core count — and recommends a local coding model because it's both installed and fits in memory.
Two war stories
The stale module that wouldn't die
After a mid-session reformat, the app went white with does not provide an export named 'DocumentsView'. TypeScript was clean. ESLint was clean. A production build was clean. So the export was real — the dev server's hot-module graph was stuck on a broken cached version.
Two fixes. First: stop mixing a value export and a type export in one component file — that's a known Fast-Refresh trigger. Moved the type to a types module. Second, and the one that actually cleared it: restart the dev server, not the browser. A hard refresh fixes the page. It does not rebuild the bundler's graph.
Dark panel in light mode
The new system panel rendered dark even in light theme. Cause: I'd written var(--bg-card, #16161a) — but --bg-card doesn't exist in the token system, so it always fell through to the hardcoded dark hex. Switched to the real tokens.
A CSS fallback to a hardcoded color is a bug waiting for a theme switch. The fallback fires silently and looks intentional.
What's next
Two big features, sharing one foundation — generated documents plus an append-only audit trail. Build the primitive once:
Conflict decision + audit. Pick the winning rule; the other is marked superseded (not deleted) with rationale, author, and timestamp. Emit a decision record, audit rows, and a SUPERSEDES edge in the graph.
Codebase analysis → documents. "Analyze the codebase and write me a design document" — plus a recommendations report and a bugs report — each saved as a durable document, not an ephemeral chat answer.
Both plug into the same primitive. That's the plan.
Build log entry from a daily journal. Written the evening of the work, lightly edited.