@kolkrabbi/kol-workshop 0.1.2 → 0.1.4

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.1.2",
3
+ "version": "0.1.4",
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.1",
16
+ "@kolkrabbi/kol-brand": "^0.1.2",
17
17
  "@kolkrabbi/kol-icons": "0.5.0",
18
18
  "@kolkrabbi/kol-component": "0.7.0",
19
- "@kolkrabbi/kol-framework": "0.3.2",
20
- "@kolkrabbi/kol-theme": "0.7.1"
19
+ "@kolkrabbi/kol-theme": "0.7.2",
20
+ "@kolkrabbi/kol-framework": "0.3.2"
21
21
  },
22
22
  "peerDependencies": {
23
23
  "react": "^18.3.0 || ^19.0.0",
@@ -27,14 +27,14 @@ const WorkshopDefaultSidebar = ({ routes = [], basePath = '/workshop' }) => {
27
27
  <div className="space-y-4">
28
28
  {siblings.length > 1 && (
29
29
  <div>
30
- <div className="shell-sidebar-label">{parentRoute.label}</div>
30
+ <div className="shell-sidebar-label kol-helper-10 text-meta">{parentRoute.label}</div>
31
31
  <nav className="space-y-0">
32
32
  {siblings.map(child => (
33
33
  <Link
34
34
  key={child.id}
35
35
  to={`${basePath}/${child.path}`}
36
- className={`shell-sidebar-link block ${
37
- location.pathname === `${basePath}/${child.path}` ? 'active' : ''
36
+ className={`shell-sidebar-link block kol-mono-14 ${
37
+ location.pathname === `${basePath}/${child.path}` ? 'text-emphasis' : 'text-body'
38
38
  }`}
39
39
  >
40
40
  {child.label}
@@ -46,16 +46,16 @@ const WorkshopDefaultSidebar = ({ routes = [], basePath = '/workshop' }) => {
46
46
 
47
47
  {links && (
48
48
  <div>
49
- <div className="shell-sidebar-label">Repository</div>
49
+ <div className="shell-sidebar-label kol-helper-10 text-meta">Repository</div>
50
50
  <div className="space-y-1">
51
51
  {links.live && (
52
- <a href={links.live} target="_blank" rel="noopener noreferrer" className="shell-sidebar-action">
52
+ <a href={links.live} target="_blank" rel="noopener noreferrer" className="shell-sidebar-action kol-mono-14 text-body">
53
53
  <Icon name="external-link" size={14} />
54
54
  Live site
55
55
  </a>
56
56
  )}
57
57
  {links.repo && (
58
- <a href={links.repo} target="_blank" rel="noopener noreferrer" className="shell-sidebar-action">
58
+ <a href={links.repo} target="_blank" rel="noopener noreferrer" className="shell-sidebar-action kol-mono-14 text-body">
59
59
  <Icon name="external-link" size={14} />
60
60
  GitHub
61
61
  </a>
@@ -65,15 +65,15 @@ const WorkshopDefaultSidebar = ({ routes = [], basePath = '/workshop' }) => {
65
65
  )}
66
66
 
67
67
  <div>
68
- <div className="shell-sidebar-label">Quick actions</div>
68
+ <div className="shell-sidebar-label kol-helper-10 text-meta">Quick actions</div>
69
69
  <div className="space-y-1">
70
- <Link to={basePath} className="shell-sidebar-action">
70
+ <Link to={basePath} className="shell-sidebar-action kol-mono-14 text-body">
71
71
  <Icon name="layout" size={14} />
72
72
  Workshop home
73
73
  </Link>
74
74
  <button
75
75
  type="button"
76
- className="shell-sidebar-action"
76
+ className="shell-sidebar-action kol-mono-14 text-body"
77
77
  onClick={() => navigator.clipboard.writeText(window.location.pathname)}
78
78
  title="Copy page path to clipboard"
79
79
  >
@@ -62,7 +62,7 @@ const DocsSidebar = ({ inventory = [], docHref, basePath, onNavigate, collapsed,
62
62
  return (
63
63
  <div className="space-y-4">
64
64
  <div className="shell-sidebar-toggle shell-sidebar-label" style={{ justifyContent: 'space-between', paddingRight: '4px' }}>
65
- <Link to={docHref()} className="shell-sidebar-label" onClick={(e) => {
65
+ <Link to={docHref()} className="shell-sidebar-label kol-helper-10 text-meta" onClick={(e) => {
66
66
  if (collapsed && onToggle) onToggle()
67
67
  if (onNavigate) onNavigate(e)
68
68
  }}>
@@ -92,7 +92,7 @@ const DocsSidebar = ({ inventory = [], docHref, basePath, onNavigate, collapsed,
92
92
  <div key={major} className="shell-nav-group">
93
93
  <button
94
94
  type="button"
95
- className="shell-nav-group-header w-full text-left"
95
+ className="shell-nav-group-header w-full text-left kol-helper-14 text-body"
96
96
  onClick={() => toggleGroup(major)}
97
97
  >
98
98
  <span className="flex items-center gap-2">
@@ -103,7 +103,7 @@ const DocsSidebar = ({ inventory = [], docHref, basePath, onNavigate, collapsed,
103
103
  />
104
104
  {categoryLabels[major] || 'Other'}
105
105
  </span>
106
- <span className="shell-nav-group-count">({docs.length})</span>
106
+ <span className="kol-helper-12 text-subtle">({docs.length})</span>
107
107
  </button>
108
108
  {!isCollapsed && (
109
109
  <div className="shell-nav-items">
@@ -113,11 +113,11 @@ const DocsSidebar = ({ inventory = [], docHref, basePath, onNavigate, collapsed,
113
113
  <Link
114
114
  key={d.id}
115
115
  to={docHref(d.id)}
116
- className={`shell-nav-item shell-nav-item-doc ${isActive ? 'active' : ''}`}
116
+ className={`shell-nav-item shell-nav-item-doc kol-mono-14 ${isActive ? 'text-emphasis' : 'text-body'}`}
117
117
  onClick={onNavigate}
118
118
  >
119
119
  <span className="shell-nav-item-title">{cleanTitle(d.title, d.id)}</span>
120
- <span className="shell-nav-item-id">{extractDocNumber(d.id)}</span>
120
+ <span className="shell-nav-item-id kol-helper-10 text-subtle">{extractDocNumber(d.id)}</span>
121
121
  </Link>
122
122
  )
123
123
  })}
@@ -1,7 +1,7 @@
1
1
  const join = (...classes) => classes.filter(Boolean).join(' ')
2
2
 
3
3
  const DocsArticle = ({ children, className }) => (
4
- <article className={join('docs-article', className)}>{children}</article>
4
+ <article className={join('docs-article kol-prose', className)}>{children}</article>
5
5
  )
6
6
 
7
7
  export default DocsArticle
@@ -33,18 +33,18 @@ const DocsFrontmatter = ({ metadata, docId }) => {
33
33
 
34
34
  return (
35
35
  <div className="docs-frontmatter">
36
- <p className="shell-sidebar-label">Frontmatter</p>
36
+ <p className="shell-sidebar-label kol-helper-10 text-meta">Frontmatter</p>
37
37
  {fields.map((key) => {
38
38
  const value = metadata[key]
39
39
  const icon = FIELD_ICONS[key]
40
40
 
41
41
  return (
42
42
  <div key={key} className="docs-frontmatter-row">
43
- <span className="docs-frontmatter-key">
43
+ <span className="docs-frontmatter-key kol-helper-12 text-meta">
44
44
  {icon && <Icon name={icon} size={14} />}
45
45
  {key}
46
46
  </span>
47
- <span className="docs-frontmatter-value">
47
+ <span className="kol-mono-12 text-strong">
48
48
  {key === 'tags' && Array.isArray(value) ? (
49
49
  <span className="flex flex-wrap gap-1.5">
50
50
  {value.map((tag) => (
@@ -2,10 +2,10 @@ const join = (...classes) => classes.filter(Boolean).join(' ')
2
2
 
3
3
  const DocsHeader = ({ title, subtitle, meta = [], className }) => (
4
4
  <header className={join('docs-header', className)}>
5
- {title ? <h1 className="docs-title">{title}</h1> : null}
6
- {subtitle ? <p className="docs-subtitle">{subtitle}</p> : null}
5
+ {title ? <h1 className="kol-sans-heading-02 text-strong">{title}</h1> : null}
6
+ {subtitle ? <p className="kol-mono-14 text-meta">{subtitle}</p> : null}
7
7
  {Array.isArray(meta) && meta.length > 0 ? (
8
- <div className="docs-meta">
8
+ <div className="docs-meta kol-helper-10 text-body">
9
9
  {meta.map(({ label, value }) => (
10
10
  <span key={label}>
11
11
  <strong>{label}</strong> {value}
@@ -13,7 +13,7 @@ const SidebarSection = ({ sectionKey, label, collapsedSections, toggleSection, c
13
13
  <div>
14
14
  <button
15
15
  type="button"
16
- className="shell-sidebar-toggle shell-sidebar-label"
16
+ className="shell-sidebar-toggle shell-sidebar-label kol-helper-10 text-meta"
17
17
  onClick={() => toggleSection(sectionKey)}
18
18
  >
19
19
  {label}
@@ -48,23 +48,23 @@ const DocReaderSidebar = ({ toc, allTags, docId, docsIndexHref, componentsHref,
48
48
  <div className="space-y-1">
49
49
  <button
50
50
  type="button"
51
- className="shell-sidebar-action"
51
+ className="shell-sidebar-action kol-mono-14 text-body"
52
52
  onClick={() => navigate(-1)}
53
53
  >
54
54
  <Icon name="arrow-left" size={14} />
55
55
  Back
56
56
  </button>
57
- <Link to={docsIndexHref} className="shell-sidebar-action">
57
+ <Link to={docsIndexHref} className="shell-sidebar-action kol-mono-14 text-body">
58
58
  <Icon name="book-open" size={14} />
59
59
  All documentation
60
60
  </Link>
61
- <Link to={componentsHref} className="shell-sidebar-action">
61
+ <Link to={componentsHref} className="shell-sidebar-action kol-mono-14 text-body">
62
62
  <Icon name="grid" size={14} />
63
63
  View components
64
64
  </Link>
65
65
  <button
66
66
  type="button"
67
- className="shell-sidebar-action"
67
+ className="shell-sidebar-action kol-mono-14 text-body"
68
68
  onClick={() => navigator.clipboard.writeText(docFilePath(docId))}
69
69
  title="Copy file path to clipboard"
70
70
  >
@@ -243,7 +243,7 @@ const DocumentationReader = ({
243
243
  <DocsArticle>
244
244
  <DocsFrontmatter metadata={doc.metadata} docId={docId} />
245
245
  {docTitle && (
246
- <h1 className="docs-title">{docTitle}</h1>
246
+ <h1>{docTitle}</h1>
247
247
  )}
248
248
  {/* Render intro blocks (excluding H1 which is docTitle) */}
249
249
  {introBlocks.filter(b => b.type !== 'heading1').map((block, index) => {
@@ -83,7 +83,7 @@ export const renderInlineTokens = (
83
83
  <Link
84
84
  key={tokenKey}
85
85
  to={tagHref(token.tag)}
86
- className="inline-tag-pill"
86
+ className="inline-tag-pill kol-helper-12 bg-fg-08 text-strong"
87
87
  >
88
88
  #{token.tag}
89
89
  </Link>
@@ -36,6 +36,20 @@ const processInlineMarkdown = (text) => {
36
36
  continue
37
37
  }
38
38
 
39
+ // Wikilink: [[target]] / [[target|display]] / [[target#anchor|display]] (Obsidian).
40
+ // Emitted as a .md link so it routes through the same resolveDocLink cross-link path.
41
+ const wikiMatch = remaining.match(/^\[\[([^\]|#]+)(?:#([^\]|]+))?(?:\|([^\]]+))?\]\]/)
42
+ if (wikiMatch) {
43
+ const target = wikiMatch[1].trim().replace(/\.md$/, '')
44
+ const anchor = wikiMatch[2]
45
+ ? wikiMatch[2].trim().toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '')
46
+ : ''
47
+ const display = wikiMatch[3] ? wikiMatch[3].trim() : (wikiMatch[1].trim().split('/').pop())
48
+ tokens.push({ type: 'link', text: display, url: `${target}.md${anchor ? `#${anchor}` : ''}` })
49
+ remaining = remaining.slice(wikiMatch[0].length)
50
+ continue
51
+ }
52
+
39
53
  // Link: [text](url)
40
54
  const linkMatch = remaining.match(/^\[([^\]]+)\]\(([^)]+)\)/)
41
55
  if (linkMatch) {
@@ -67,14 +67,14 @@ const ShellSidebar = ({ routes = [], basePath = '/', onNavigate, label = 'Naviga
67
67
  <div className="space-y-4">
68
68
  <div className="shell-sidebar-toggle shell-sidebar-label" style={{ justifyContent: 'space-between', paddingRight: '4px' }}>
69
69
  {labelTo ? (
70
- <Link to={labelTo} className="shell-sidebar-label" onClick={(e) => {
70
+ <Link to={labelTo} className="shell-sidebar-label kol-helper-10 text-meta" onClick={(e) => {
71
71
  if (navCollapsed && handleToggle) handleToggle()
72
72
  if (onNavigate) onNavigate(e)
73
73
  }}>
74
74
  {label}
75
75
  </Link>
76
76
  ) : (
77
- <button type="button" onClick={handleToggle}>{label}</button>
77
+ <button type="button" className="kol-helper-10 text-meta" onClick={handleToggle}>{label}</button>
78
78
  )}
79
79
  <button
80
80
  type="button"
@@ -99,7 +99,7 @@ const ShellSidebar = ({ routes = [], basePath = '/', onNavigate, label = 'Naviga
99
99
  <div key={route.id} className="shell-nav-group">
100
100
  <button
101
101
  type="button"
102
- className="shell-nav-group-header w-full text-left"
102
+ className="shell-nav-group-header w-full text-left kol-helper-14 text-body"
103
103
  onClick={() => handleSectionClick(route)}
104
104
  >
105
105
  <span className="flex items-center gap-2">
@@ -111,7 +111,7 @@ const ShellSidebar = ({ routes = [], basePath = '/', onNavigate, label = 'Naviga
111
111
  {route.label}
112
112
  </span>
113
113
  {route.children?.length > 0 && (
114
- <span className="shell-nav-group-count">({route.children.length})</span>
114
+ <span className="kol-helper-12 text-subtle">({route.children.length})</span>
115
115
  )}
116
116
  </button>
117
117
 
@@ -125,7 +125,7 @@ const ShellSidebar = ({ routes = [], basePath = '/', onNavigate, label = 'Naviga
125
125
  to={childPath}
126
126
  end
127
127
  className={({ isActive }) =>
128
- `shell-nav-item${isActive ? ' active' : ''}`
128
+ `shell-nav-item kol-mono-14 ${isActive ? 'text-emphasis' : 'text-body'}`
129
129
  }
130
130
  onClick={onNavigate}
131
131
  >
@@ -217,7 +217,7 @@ const TagGraph = ({ docs, activeTag, onTagClick, allDocs, tagHref = defaultTagHr
217
217
 
218
218
  if (graphData.nodes.length === 0) {
219
219
  return (
220
- <div className="text-fg-48 kol-mono-xs py-4 px-2">
220
+ <div className="text-fg-48 kol-mono-12 py-4 px-2">
221
221
  No tags found.
222
222
  </div>
223
223
  )
@@ -232,7 +232,7 @@ const TagGraph = ({ docs, activeTag, onTagClick, allDocs, tagHref = defaultTagHr
232
232
  className="tag-graph-svg"
233
233
  />
234
234
  {hoveredNode && (
235
- <div className="tag-graph-tooltip">
235
+ <div className="tag-graph-tooltip kol-helper-10 text-emphasis">
236
236
  #{hoveredNode.id} ({hoveredNode.count} {hoveredNode.count === 1 ? 'doc' : 'docs'})
237
237
  </div>
238
238
  )}
@@ -1,7 +1,11 @@
1
1
  import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'
2
2
  import { useLocation } from 'react-router-dom'
3
3
 
4
- const TagModeContext = createContext(null)
4
+ // Singleton across duplicate module instances: Vite dev can serve this file
5
+ // under two URLs (barrel `?v=` vs direct import), which would otherwise create
6
+ // two distinct contexts and break useTagMode. Stash on globalThis so every
7
+ // instance shares one context object. Harmless in prod (single instance).
8
+ const TagModeContext = (globalThis.__KOL_TAGMODE_CONTEXT__ ||= createContext(null))
5
9
 
6
10
  /**
7
11
  * Default route helpers. These are the ONLY place the old hardcoded workshop
@@ -96,10 +100,26 @@ export const TagModeProvider = ({
96
100
  )
97
101
  }
98
102
 
99
- export const useTagMode = () => {
100
- const ctx = useContext(TagModeContext)
101
- if (!ctx) throw new Error('useTagMode must be used within TagModeProvider')
102
- return ctx
103
+ /**
104
+ * No-provider fallback: tag mode degrades to inert (tags render, clicks no-op)
105
+ * instead of crashing the reader. Wrap with TagModeProvider to enable it.
106
+ */
107
+ const noop = () => {}
108
+ const DEFAULT_TAG_MODE = {
109
+ isOpen: false,
110
+ activeTags: [],
111
+ activeTag: null,
112
+ openTagMode: noop,
113
+ closeTagMode: noop,
114
+ toggleTag: noop,
115
+ removeTag: noop,
116
+ clearTags: noop,
117
+ setActiveTag: noop,
118
+ inventory: [],
119
+ docHref: defaultDocHref,
120
+ tagHref: defaultTagHref
103
121
  }
104
122
 
123
+ export const useTagMode = () => useContext(TagModeContext) ?? DEFAULT_TAG_MODE
124
+
105
125
  export default TagModeContext
@@ -141,7 +141,7 @@ const TagModeOverlay = () => {
141
141
  </button>
142
142
  ))}
143
143
  {visibleTags.length === 0 && (
144
- <p className="text-fg-48 kol-mono-xs py-4">No tags matching "{search}"</p>
144
+ <p className="text-fg-48 kol-mono-12 py-4">No tags matching "{search}"</p>
145
145
  )}
146
146
  </div>
147
147