Repository navigation
Add KB Authoring: a third deck, and exports that survive another system - #1
Merged
Merged
Conversation
Articles are ordinary notes carrying a kb_type frontmatter key. There is no second store and no schema change -- vault.parse/dump already round-trip arbitrary extra keys -- so deleting .index/ still loses nothing and a KB article opened in any editor is just a note with some extra headers. Templates and the metadata schema are data, not code. Six article shapes ship (troubleshooting, how-to, reference, known-issue, faq, announcement) and adding a seventh is one entry in TEMPLATES: the authoring form, the structure panel and the export header are all generated from those lists. Export is the point. A KB article has to leave for a system that has never heard of Tephra's stylesheet, so this is a deliberate downgrade rather than a dump of the app's HTML -- done once, on purpose, instead of discovered a paragraph at a time after pasting. The ServiceNow target inlines every style on the element and carries table attributes as well as CSS; a callout becomes a single-cell bordered table, because a div flattens in a sanitiser and a warning that flattens into a paragraph stops reading as a warning. The standalone target is the faithful artifact: self-contained, responsive, images inlined, opens anywhere. Markdown and plain text cover the fields that accept nothing else. Syntax parsing is imported from render.py, never copied, so an article cannot mean one thing on screen and another on export. Only emission differs, which is the part that genuinely has to. Images are never silently dropped. Each becomes a numbered placeholder plus a manifest row, and the numbering is deferred until the article is finished because callouts and sheets are extracted before the passes that follow them -- the order media is met is not the order it appears in, and an author matching "Image 3" to the third gap on the page needs document order. The three decks are now driven by one setDeck(name) rather than a boolean each: two independent toggles can put two opaque full-deck panes on screen at once, and the loser is invisible rather than visibly wrong. 51 suites, 1932 checks, all passing -- 156 of them new. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013iCA3u13kQ3xFvjot88Knp
A screenshot of the real "Question and Answer" form settled what the exporter had been guessing at. The destination KB has no article body. It has five separate rich-text boxes -- Question, Environment, Answer, Additional Information, Internal Notes -- plus a Short description input and a plain Meta textarea capped at 4000 characters. One form serves every article kind; Category is a taxonomy, not a layout. So the ServiceNow target no longer produces a document. It produces `parts`: one fragment per box, in the order the form asks for them, and the export panel is a walk straight down that form with a copy button per field and a `<>` button beside each rich-text one. The preview renders the form rather than a flowing page, because that page does not exist over there -- and carries no stylesheet beyond the box chrome, so what survives in the preview is what survives the paste. Every template section now names the field it feeds, and `kb_fieldmap` in an article's own frontmatter overrides that per article, for the section the template never imagined. An unmapped heading falls back to Answer rather than being dropped: content that quietly goes nowhere is the failure mode worth engineering against. A new question-answer template mirrors the form section for section. Two things fell out of the split. Media numbering had to be decided once over the whole document and then applied per fragment -- _Media.finalize now returns a resolver rather than a rewritten string, because numbering each box on its own restarts every image at 1. And a field fed by exactly one section drops that heading, since the field is the heading; fed by several, the headings stay as h3, since the box is already playing the h2's part. Live testing caught what the tests did not: the mapping editor listed a "Sources" row that no copy button could honour, because field_plan sectioned the raw note while the exporter stripped `## Sources` first. Both now go through one exportable_body(), with a test asserting the plan and the parts name exactly the same sections. The metadata schema is the form's own fields now -- Knowledge base, Category, Audience, KCS Confidence, Valid to, Ownership Group, Pure Product(s)/Feature(s), Purity Branch(es) Affected, Purity Version(s) with Fix, Meta -- replacing the generic set that was standing in for them. Audience, Category and Knowledge base stay free text until their dropdown values are known. 51 suites, 1973 checks, all passing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013iCA3u13kQ3xFvjot88Knp
The deck could turn a note into an article and turn it back again, but never
get rid of one -- the only way to delete a KB article was to leave the deck,
find the note on the Tephra side, and delete it there.
Delete now sits beside Release, and the two are coloured apart because they
sound alike and do very different things: Release drops the kb_ fields and
leaves the note exactly as it was, Delete moves the whole note to the vault
trash. It reuses DELETE /api/notes/{slug} rather than growing a KB-specific
deletion path, so an article is trashed and recoverable on the same terms as
any other note. Same arm-then-confirm gesture as the note editor's own Delete
chip, since it is the same gesture -- a confirm dialog only trains people to
dismiss it.
The pending autosave is cancelled before the delete goes out. A debounced
flush that landed afterwards would write the file straight back out of the
trash, which is how a deleted note comes back from the dead; there is a test
for exactly that.
51 suites, 1989 checks, all passing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013iCA3u13kQ3xFvjot88Knp
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.
Articles are ordinary notes carrying a kb_type frontmatter key. There is no second store and no schema change -- vault.parse/dump already round-trip arbitrary extra keys -- so deleting .index/ still loses nothing and a KB article opened in any editor is just a note with some extra headers.
Templates and the metadata schema are data, not code. Six article shapes ship (troubleshooting, how-to, reference, known-issue, faq, announcement) and adding a seventh is one entry in TEMPLATES: the authoring form, the structure panel and the export header are all generated from those lists.
Export is the point. A KB article has to leave for a system that has never heard of Tephra's stylesheet, so this is a deliberate downgrade rather than a dump of the app's HTML -- done once, on purpose, instead of discovered a paragraph at a time after pasting. The ServiceNow target inlines every style on the element and carries table attributes as well as CSS; a callout becomes a single-cell bordered table, because a div flattens in a sanitiser and a warning that flattens into a paragraph stops reading as a warning. The standalone target is the faithful artifact: self-contained, responsive, images inlined, opens anywhere. Markdown and plain text cover the fields that accept nothing else.
Syntax parsing is imported from render.py, never copied, so an article cannot mean one thing on screen and another on export. Only emission differs, which is the part that genuinely has to.
Images are never silently dropped. Each becomes a numbered placeholder plus a manifest row, and the numbering is deferred until the article is finished because callouts and sheets are extracted before the passes that follow them -- the order media is met is not the order it appears in, and an author matching "Image 3" to the third gap on the page needs document order.
The three decks are now driven by one setDeck(name) rather than a boolean each: two independent toggles can put two opaque full-deck panes on screen at once, and the loser is invisible rather than visibly wrong.
51 suites, 1932 checks, all passing -- 156 of them new.
Claude-Session: https://claude.ai/code/session_013iCA3u13kQ3xFvjot88Knp