@kolkrabbi/kol-workshop 0.37.0 → 0.38.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.37.0",
3
+ "version": "0.38.0",
4
4
  "private": false,
5
5
  "description": "Docs viewer, shell and tag system",
6
6
  "license": "MIT",
@@ -13,14 +13,14 @@
13
13
  },
14
14
  "dependencies": {
15
15
  "d3": "^7.9.0",
16
- "@kolkrabbi/kol-markdown": "^0.1.2",
17
- "@kolkrabbi/kol-brand": "^0.1.3",
18
- "@kolkrabbi/kol-search": "^0.3.0"
16
+ "@kolkrabbi/kol-markdown": "^0.1.3",
17
+ "@kolkrabbi/kol-search": "^0.3.0",
18
+ "@kolkrabbi/kol-brand": "^0.1.3"
19
19
  },
20
20
  "peerDependencies": {
21
21
  "@kolkrabbi/kol-component": ">=0.237.0",
22
22
  "@kolkrabbi/kol-framework": ">=0.45.0",
23
- "@kolkrabbi/kol-theme": ">=0.163.0",
23
+ "@kolkrabbi/kol-theme": ">=0.165.0",
24
24
  "@kolkrabbi/kol-icons": ">=0.18.0",
25
25
  "gsap": "^3.13.0",
26
26
  "react": "^18.3.0 || ^19.0.0",
@@ -28,10 +28,10 @@
28
28
  "react-router-dom": "^6.0.0 || ^7.0.0"
29
29
  },
30
30
  "devDependencies": {
31
- "@kolkrabbi/kol-framework": "^0.48.0",
32
- "@kolkrabbi/kol-theme": "^0.163.0",
33
- "@kolkrabbi/kol-component": "^0.237.0",
34
- "@kolkrabbi/kol-icons": "^0.33.0"
31
+ "@kolkrabbi/kol-component": "^0.239.0",
32
+ "@kolkrabbi/kol-icons": "^0.33.0",
33
+ "@kolkrabbi/kol-theme": "^0.165.0",
34
+ "@kolkrabbi/kol-framework": "^0.49.0"
35
35
  },
36
36
  "files": [
37
37
  "src",
@@ -63,7 +63,8 @@ export const renderInlineTokens = (
63
63
  return token.text
64
64
  }
65
65
  return (
66
- <a key={tokenKey} href={token.url} className={linkCls}>
66
+ /* a link that leaves the site opens a new tab (2026-10-02); a relative or `#` link stays */
67
+ <a key={tokenKey} href={token.url} className={linkCls} {...(/^https?:/.test(token.url) ? { target: '_blank', rel: 'noreferrer' } : null)}>
67
68
  {token.text}
68
69
  </a>
69
70
  )
@@ -2,7 +2,7 @@ import { createContext, useContext, useMemo, useRef, useState, useEffect, Suspen
2
2
  import { Outlet, Link, useLocation, useNavigate, useNavigationType } from 'react-router-dom'
3
3
  import { ShellHeader } from '@kolkrabbi/kol-framework'
4
4
  import ShellSidebar, { RAIL_FOLD_EVENT, ShellRailModeContext } from './ShellSidebar.jsx'
5
- import { IconFrame, ShellDrawer, ShellSearchOverlay, ShortcutsOverlay, SettingsPanel, SettingsSections, SettingsSwitch, Tooltip, useDragResize } from '@kolkrabbi/kol-component'
5
+ import { FullscreenOverlay, IconFrame, ShellDrawer, ShellSearchOverlay, ShortcutsOverlay, SettingsPanel, SettingsSections, SettingsSwitch, Tooltip, useDragResize } from '@kolkrabbi/kol-component'
6
6
  import { createIndex, search } from '@kolkrabbi/kol-search'
7
7
  import { useTagMode } from '../tags/TagModeContext.jsx'
8
8
  import TagModeOverlay from '../tags/TagModeOverlay.jsx'
@@ -262,6 +262,7 @@ const ShellLayout = ({ routes = [], basePath = '/', brand: brandProp, brandLogoS
262
262
  const [isFullHeight, setIsFullHeight] = useState(false)
263
263
  const [contentWidth, setContentWidth] = useState('canvas')
264
264
  const [isShortcutsOpen, setIsShortcutsOpen] = useState(false)
265
+ const [isTreeOpen, setIsTreeOpen] = useState(false)
265
266
  const location = useLocation()
266
267
  const navigate = useNavigate()
267
268
 
@@ -318,6 +319,7 @@ const ShellLayout = ({ routes = [], basePath = '/', brand: brandProp, brandLogoS
318
319
  { id: 'toc', label: 'Right rail', combo: ']' },
319
320
  { id: 'rails', label: 'Both rails', combo: '\\' },
320
321
  { id: 'fold', label: 'Fold / expand all', combo: 'C' },
322
+ { id: 'tree', label: 'Whole tree', combo: 'T' },
321
323
  { id: 'esc', label: 'Close', combo: 'Esc' },
322
324
  ] },
323
325
  ...(shortcuts.length ? [{ section: 'Page', items: shortcuts.map(({ id, label, combo }) => ({ id, label, combo })) }] : []),
@@ -366,6 +368,11 @@ const ShellLayout = ({ routes = [], basePath = '/', brand: brandProp, brandLogoS
366
368
  e.preventDefault()
367
369
  foldedRef.current = !foldedRef.current
368
370
  window.dispatchEvent(new CustomEvent(RAIL_FOLD_EVENT, { detail: { collapsed: foldedRef.current } }))
371
+ } else if (e.key === 't' || e.key === 'T') {
372
+ /* THE WHOLE TREE (user 2026-10-02: *"C … is just about the visual tree per space, I was
373
+ * talking about a shortcut to get the entire tree in any given view"*) */
374
+ e.preventDefault()
375
+ setIsTreeOpen((v) => !v)
369
376
  } else if (shortcutsRef.current.some((s) => s.key === e.key)) {
370
377
  e.preventDefault()
371
378
  shortcutsRef.current.find((s) => s.key === e.key).run()
@@ -655,6 +662,25 @@ const ShellLayout = ({ routes = [], basePath = '/', brand: brandProp, brandLogoS
655
662
 
656
663
  {isShortcutsOpen && <ShortcutsOverlay shortcuts={SHORTCUTS} onClose={() => setIsShortcutsOpen(false)} />}
657
664
 
665
+ {/* THE WHOLE TREE, FROM ANY PAGE (`T`, 2026-10-02): every space's rail side by side, every
666
+ * fold open, over the page like the search modal. Each column is the consumer's own rail
667
+ * for that space (`renderSidebar`) drawn in the `full` rail mode, so it cannot list
668
+ * anything the rails do not. A row closes the sheet and goes there. */}
669
+ <FullscreenOverlay open={isTreeOpen} onClose={() => setIsTreeOpen(false)} scrim>
670
+ <div className="shell-tree bg-surface-primary">
671
+ {routes.map((route) => (
672
+ <section key={route.id ?? route.path} className="shell-tree-space">
673
+ <Link to={route.path?.startsWith('/') ? route.path : `${basePath}/${route.path ?? ''}`} className="kol-doc-eyebrow text-emphasis" onClick={() => setIsTreeOpen(false)}>{route.label}</Link>
674
+ <ShellRailModeContext.Provider value="full">
675
+ {renderSidebar
676
+ ? renderSidebar({ activeRoute: route, onNavigate: () => setIsTreeOpen(false), mode: 'full' })
677
+ : <ShellSidebar routes={route.children ?? []} basePath={basePath} label={null} onNavigate={() => setIsTreeOpen(false)} />}
678
+ </ShellRailModeContext.Provider>
679
+ </section>
680
+ ))}
681
+ </div>
682
+ </FullscreenOverlay>
683
+
658
684
  {/* THE SHELL'S SETTINGS (2026-09-28, user: "might we also want to utilize a
659
685
  * settings page or settings sidebar … to offload some functional
660
686
  * settings, search settings, sidenav settings"). A right drawer over
@@ -30,7 +30,8 @@ export const RAIL_FOLD_EVENT = 'kol-rail-fold'
30
30
  * we dont use it, I want to see it"*). Beside visible and hidden: `icons` — the rail is a narrow
31
31
  * strip, one glyph per group, and opens on hover. The column that holds the rail (`ShellNavColumn`)
32
32
  * provides the state; every ShellSidebar inside it reads it, so a consumer's rail takes the state
33
- * without passing a prop. Outside a provider a rail is `open`, as it always was. */
33
+ * without passing a prop. Outside a provider a rail is `open`, as it always was. A third value,
34
+ * `full`, is the whole-tree sheet's: an open rail with every fold open. */
34
35
  export const ShellRailModeContext = createContext('open')
35
36
 
36
37
  const ShellSidebar = ({ routes = [], basePath = '/', onNavigate, label = 'Navigation', labelTo, collapsed, onToggle, defaultCollapsed = false }) => {
@@ -48,8 +49,8 @@ const ShellSidebar = ({ routes = [], basePath = '/', onNavigate, label = 'Naviga
48
49
 
49
50
  /* THE RAIL FOLLOWS YOU — INTO A CHILD ONLY (2026-09-30, reversing the 2026-09-28 "open the
50
51
  * group you are in" on its landing page too). Landing on a page INSIDE a chapter opens that
51
- * chapter and folds the rest; a chapter's own home keeps that chapter open (2026-10-01, see
52
- * `chainTo`). Every other chapter starts
52
+ * chapter and folds the rest; a chapter's own home leaves that chapter's fold alone (2026-10-02,
53
+ * see `chainTo`). Every other chapter starts
53
54
  * folded — including ones this rail has never seen (a Group-by switch hands it new ids, and an
54
55
  * unknown id used to read as open, so every chapter sprang open).
55
56
  *
@@ -72,28 +73,32 @@ const ShellSidebar = ({ routes = [], basePath = '/', onNavigate, label = 'Naviga
72
73
  /* deepest first: `/sets` holds `/sets/app-shell` by prefix, but the set's own row is the place */
73
74
  const deeper = chainTo(r.children, [...trail, r])
74
75
  if (deeper) return deeper
75
- if (r.children.some(isHere)) return [...trail, r]
76
- /* A GROUP'S OWN HOME KEEPS IT OPEN (user 2026-10-01: *"I dont want to collapse the list
77
- * like that when I just want to go to atoms home and still see the atoms list"* — reverses
78
- * the 2026-09-30 "a chapter's own home opens nothing"). The label is a link; only the
79
- * chevron folds. */
80
- if (r.path && getChildPath(r, basePath).replace(/\/$/, '') === normalizedPath) return [...trail, r]
76
+ /* A GROUP'S OWN HOME LEAVES ITS FOLD AS IT WAS (user 2026-10-02, said many times: *"just
77
+ * clicking a title opens the home, if I want to expand then I fucking click the expand
78
+ * chevron"*). The 2026-10-01 reading opened the group on its own home, so a title click
79
+ * expanded its children; the 2026-09-30 one folded it. Neither: the way down to it opens,
80
+ * the group itself stays as the chevron left it — open stays open, folded stays folded.
81
+ * Checked BEFORE the children: a vault chapter's home is also its `About` row, and reading
82
+ * that as "a child is here" opened every Docs chapter on a title click.
83
+ * `validate:rail-pages` P5 holds it. */
84
+ if (r.path && getChildPath(r, basePath).replace(/\/$/, '') === normalizedPath) return { open: trail, keep: r.id }
85
+ if (r.children.some(isHere)) return { open: [...trail, r] }
81
86
  }
82
87
  return null
83
88
  }
84
- const followed = () => {
85
- const chain = chainTo(routes)
86
- if (!chain) return null
87
- const open = new Set(chain.map((g) => g.id))
88
- return Object.fromEntries(groupsOf(routes).map((g) => [g.id, !open.has(g.id)]))
89
+ const followed = (prev = {}) => {
90
+ const at = chainTo(routes)
91
+ if (!at) return null
92
+ const open = new Set(at.open.map((g) => g.id))
93
+ return Object.fromEntries(groupsOf(routes).map((g) => [g.id, g.id === at.keep ? (prev[g.id] ?? true) : !open.has(g.id)]))
89
94
  }
90
95
  const [collapsedSections, setCollapsedSections] = useState(() => followed() ?? {})
91
96
  useEffect(() => {
92
- const next = followed()
93
- if (next) setCollapsedSections(next)
97
+ setCollapsedSections((prev) => followed(prev) ?? prev)
94
98
  // eslint-disable-next-line react-hooks/exhaustive-deps
95
99
  }, [normalizedPath, routes, basePath])
96
- const isFolded = (route) => collapsedSections[route.id] ?? true
100
+ /* `full` — the whole-tree sheet (`T`, 2026-10-02): every fold open, nothing to follow */
101
+ const isFolded = (route) => (mode === 'full' ? false : collapsedSections[route.id] ?? true)
97
102
 
98
103
  /* FOLD ALL (2026-09-30, the names audit: *"shortcut collapse/expand the categories"*). The
99
104
  * shell's `C` key broadcasts one target state; every rail's chapters take it. */
@@ -210,7 +215,7 @@ const ShellSidebar = ({ routes = [], basePath = '/', onNavigate, label = 'Naviga
210
215
  label={label}
211
216
  to={labelTo}
212
217
  count={leafCount}
213
- collapsed={navCollapsed}
218
+ collapsed={mode === 'full' ? false : navCollapsed}
214
219
  onToggle={handleToggle}
215
220
  onNavigate={onNavigate}
216
221
  icon={Icon}