@kolkrabbi/kol-workshop 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kolkrabbi/kol-workshop",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "private": false,
5
5
  "description": "KOL workshop/docs system — a handrolled markdown engine, search, tag system (incl. a d3 tag graph), docs viewer, and docs shell. Sits above @kolkrabbi/kol-{theme,icons,component,framework}; content is consumer-injected (the package never globs docs).",
6
6
  "license": "MIT",
@@ -13,11 +13,11 @@
13
13
  },
14
14
  "dependencies": {
15
15
  "d3": "^7.9.0",
16
- "@kolkrabbi/kol-brand": "^0.1.2",
17
16
  "@kolkrabbi/kol-icons": "0.8.11",
18
- "@kolkrabbi/kol-framework": "0.9.1",
17
+ "@kolkrabbi/kol-component": "0.14.3",
18
+ "@kolkrabbi/kol-brand": "^0.1.2",
19
19
  "@kolkrabbi/kol-theme": "0.13.3",
20
- "@kolkrabbi/kol-component": "0.14.2"
20
+ "@kolkrabbi/kol-framework": "0.9.1"
21
21
  },
22
22
  "peerDependencies": {
23
23
  "react": "^18.3.0 || ^19.0.0",
@@ -21,22 +21,40 @@ const FIELD_ICONS = {
21
21
  modified: null,
22
22
  }
23
23
 
24
- /* THE kol-docs contract, in reading order — `.kol/docs-framework/01-conventions.md`:
25
- * required (title/type/status/updated/tags) then recommended (description,
26
- * aliases). `related` is excluded on purpose: the rail renders it as links.
24
+ /* Reading ORDER, not an allowlist. The kol-docs contract first
25
+ * (`.kol/docs-framework/01-conventions.md`: required title/type/status/updated/
26
+ * tags, then recommended description/aliases, then optional), legacy
27
+ * sample-dialect keys after, and ANY key not named here after that — so a field
28
+ * can never be silently dropped for being unknown.
27
29
  *
28
- * This list read ['title','category','date','tags','modified'] — the
30
+ * This was a filter over ['title','category','date','tags','modified'] — the
29
31
  * workshop-SAMPLE dialect. No kol-docs document carries category/date/modified,
30
- * so the filter below admitted title + tags and silently dropped the other
31
- * seven fields the parser had already handed it. The panel wasn't missing data;
32
- * it was screening for the wrong schema. Sample-dialect keys stay at the tail
33
- * so those docs keep rendering. */
32
+ * so it admitted title + tags and discarded the seven fields the parser had
33
+ * already handed it. The panel wasn't missing data; it was screening for the
34
+ * wrong schema, and screening is now the thing it doesn't do.
35
+ *
36
+ * `related` is the one deliberate omission — the rail renders it as live links,
37
+ * so printing the raw wikilinks here would be the same thing twice. */
34
38
  const FIELD_ORDER = [
35
- 'title', 'type', 'status', 'updated', 'created', 'verified',
36
- 'description', 'audience', 'aliases', 'tags',
37
- 'category', 'date', 'modified',
39
+ 'title', 'type', 'status', 'updated', 'tags',
40
+ 'description', 'aliases',
41
+ 'created', 'verified', 'audience', 'superseded_by', 'drift',
42
+ 'category', 'date', 'modified', 'version',
38
43
  ]
39
44
 
45
+ const HIDDEN = new Set(['related'])
46
+
47
+ /** Contract order first, then anything else the doc carries, alphabetically. */
48
+ const orderFields = (metadata) => {
49
+ const present = Object.keys(metadata).filter(
50
+ (k) => !HIDDEN.has(k) && metadata[k] != null && metadata[k] !== '' &&
51
+ !(Array.isArray(metadata[k]) && metadata[k].length === 0)
52
+ )
53
+ const known = FIELD_ORDER.filter((k) => present.includes(k))
54
+ const rest = present.filter((k) => !FIELD_ORDER.includes(k)).sort()
55
+ return [...known, ...rest]
56
+ }
57
+
40
58
  /* Every field rendered as a date. `updated`/`created`/`verified` are the
41
59
  * kol-docs names; `date`/`modified` are the sample dialect's. */
42
60
  const DATE_FIELDS = new Set(['updated', 'created', 'verified', 'date', 'modified'])
@@ -52,7 +70,7 @@ const DocsFrontmatter = ({ metadata, docId }) => {
52
70
  const { openTagMode } = useTagMode()
53
71
  if (!metadata || Object.keys(metadata).length === 0) return null
54
72
 
55
- const fields = FIELD_ORDER.filter(key => metadata[key] != null && metadata[key] !== '')
73
+ const fields = orderFields(metadata)
56
74
 
57
75
  if (fields.length === 0) return null
58
76
 
@@ -10,18 +10,41 @@ import DocsFrontmatter from './DocsFrontmatter.jsx'
10
10
  import { DocSection } from './DocKit.jsx'
11
11
  import { renderInlineTokens } from './render-tokens.jsx'
12
12
 
13
- const SidebarSection = ({ sectionKey, label, collapsedSections, toggleSection, children }) => (
14
- <div>
15
- <button
16
- type="button"
17
- className="shell-sidebar-toggle shell-sidebar-label kol-doc-eyebrow"
18
- onClick={() => toggleSection(sectionKey)}
19
- >
20
- {label}
21
- </button>
22
- {!collapsedSections[sectionKey] && children}
23
- </div>
24
- )
13
+ /**
14
+ * A rail section header. Same anatomy as the LEFT tree's group header — a
15
+ * rotating chevron, the label, and the item count pushed right — because the
16
+ * two rails are one system and a reader shouldn't have to learn each side
17
+ * separately.
18
+ *
19
+ * These were bare text. They collapsed on click with no chevron, no count and
20
+ * no hover, so the only way to discover they were interactive was to click
21
+ * something that looked inert and watch the panel vanish. An affordance that
22
+ * only announces itself after you've used it isn't one.
23
+ */
24
+ const SidebarSection = ({ sectionKey, label, count, collapsedSections, toggleSection, children }) => {
25
+ const isOpen = !collapsedSections[sectionKey]
26
+ return (
27
+ <div>
28
+ <button
29
+ type="button"
30
+ className="shell-nav-group-header w-full text-left shell-sidebar-label kol-doc-eyebrow"
31
+ onClick={() => toggleSection(sectionKey)}
32
+ aria-expanded={isOpen}
33
+ >
34
+ <span className="flex items-center gap-2">
35
+ <Icon
36
+ name="chevron-right"
37
+ size={12}
38
+ className={`transition-transform ${isOpen ? 'rotate-90' : ''}`}
39
+ />
40
+ {label}
41
+ </span>
42
+ {count > 0 && <span className="kol-mono-14 text-subtle">({count})</span>}
43
+ </button>
44
+ {isOpen && children}
45
+ </div>
46
+ )
47
+ }
25
48
 
26
49
  const DocReaderSidebar = ({ toc, allTags, related, docId, docsIndexHref, componentsHref, docFilePath }) => {
27
50
  const navigate = useNavigate()
@@ -34,6 +57,7 @@ const DocReaderSidebar = ({ toc, allTags, related, docId, docsIndexHref, compone
34
57
  <SidebarSection
35
58
  sectionKey="toc"
36
59
  label="On this page"
60
+ count={toc?.length}
37
61
  collapsedSections={collapsedSections}
38
62
  toggleSection={toggleSection}
39
63
  >
@@ -44,6 +68,7 @@ const DocReaderSidebar = ({ toc, allTags, related, docId, docsIndexHref, compone
44
68
  <SidebarSection
45
69
  sectionKey="related"
46
70
  label="Related"
71
+ count={related.length}
47
72
  collapsedSections={collapsedSections}
48
73
  toggleSection={toggleSection}
49
74
  >
@@ -100,6 +125,7 @@ const DocReaderSidebar = ({ toc, allTags, related, docId, docsIndexHref, compone
100
125
  {allTags.length > 0 && (
101
126
  <SidebarSection
102
127
  sectionKey="tags"
128
+ count={allTags?.length}
103
129
  label="Tags"
104
130
  collapsedSections={collapsedSections}
105
131
  toggleSection={toggleSection}