- Ya
- 19 Agosti 2026, 08:10 UTC
- Mwandishi
- kamo
- Ahadi ya
- 13c7bf4
The four knowledge-base screens were the last in the app still wearing the old look, and three of them stated things that were not true. /kb showed "Pinned Articles" from an endpoint that sent pin rows with the article stripped out by @JsonIgnore — the section has never appeared for anybody. Beneath it, "Recent Articles" was the first ten nodes of the article tree, in tree order, each stamped PUBLISHED with a blank date: neither recent nor necessarily published. The tree beside them collapsed itself on every navigation, because each branch held its own useState. One idea carries the rebuild: THE RAIL IS THE FILTER. A tile's count and the list it opens both come from matchesFilter in the model, so a tile reading 4 opens exactly those 4. There is no second row of chips restating the same predicates in different words, waiting to disagree. Around it: search that matches title, address and body and marks which one it hit (the old box debounced 500ms to ask a server that searched contentPlainText only, so typing an article's exact title returned nothing), a tree that remembers what you opened and can be resized, faces on every card from the member directory, and a Needs Attention panel — reviews that have waited, drafts gone quiet, published articles nobody has touched in a quarter. /kb/resources was a modal dialog rendered flat onto a route, with open={true} and an onClose that navigated away. It is a library now: a rail that weighs what the organization is storing and doubles as the type filter, tiles with real faces for video and files rather than a grid of identical grey boxes, drop-anywhere upload with real per-file progress (fetch cannot report progress, so a 40MB video looked exactly like a 4KB icon), and renaming in place instead of a modal launched from a footer. The editor's insert-media picker is now the same tiles, the same detail pane and the same useKbMedia hook in a dialog — one kit, two surfaces, so they cannot drift. /kb/reviews was a list of titles. A reviewer could see that four things were waiting and nothing else — not who wrote them, not how long they had sat there, not what they said — and deciding meant opening each article, scrolling to a panel at the bottom, and coming back. Worst of all a failed fetch rendered as "No articles pending review": the queue reported itself clear on a day it may not have been. Now the queue carries each article's age, submitter and size; the article sits beside it; and the verdict buttons are next to what they apply to. /kb/new was a form that told an author nothing. The slug field said "Auto-generated from title" and then let you save a slug the server rejects with a 400 after the article is written. The parent dropdown indented options with two spaces, which HTML collapses, so every option looked top-level. And all three visibility boxes could be ticked with AI training left off, which silently means nobody sees it anywhere. The composer answers those three questions before the work rather than after the rejection, and offers Save draft beside Create and submit. Fixed along the way, all of them visible: - KbStatusBadge shipped four hardcoded English words and four hardcoded hex pairs — a Spanish console said "Draft" in a grey no white-label palette could move. - KbPinButton captured isPinned once, so a page that learns its pin state after the first render (every page here) showed an outline pin over a pinned article forever; a failed request left the pin looking set. - The article reader read article.pinned, a field KbArticle does not have. - mediaId was declared number across the four editor node types and extractMediaIdsFromJson, while KB_MEDIA.UID is a UUID and the values have always been strings. It survived only because nothing ever did arithmetic on one; a single Number(mediaId) would have been a silent NaN. - KbMediaResource.uid had the same lie. - Media requests raced: a slow early search response could land after a fast later one and repaint the grid for a query nobody was looking at. The pure rules live in app/lib/kbConsole.ts and app/lib/kbMedia.ts with 52 tests, because vitest here collects .test.ts only — a rule written inside a .tsx is a rule with no test. What they encode throughout: an unknown is never a zero. A missing date sorts last rather than becoming the epoch, an unrecognised status paints neutral rather than green, an empty queue has no oldest item rather than a zero-day one, and a resource whose usage count never arrived warns instead of promising it is unused.