@kolkrabbi/kol-workshop 0.24.1 → 0.25.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.24.1",
3
+ "version": "0.25.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,7 +13,7 @@
13
13
  },
14
14
  "dependencies": {
15
15
  "d3": "^7.9.0",
16
- "@kolkrabbi/kol-brand": "^0.1.2"
16
+ "@kolkrabbi/kol-brand": "^0.1.3"
17
17
  },
18
18
  "peerDependencies": {
19
19
  "@kolkrabbi/kol-component": ">=0.68.1",
@@ -25,10 +25,10 @@
25
25
  "react-router-dom": "^6.0.0 || ^7.0.0"
26
26
  },
27
27
  "devDependencies": {
28
- "@kolkrabbi/kol-icons": "^0.18.0",
29
- "@kolkrabbi/kol-component": "^0.68.1",
30
- "@kolkrabbi/kol-framework": "^0.24.0",
31
- "@kolkrabbi/kol-theme": "^0.52.1"
28
+ "@kolkrabbi/kol-component": "^0.145.0",
29
+ "@kolkrabbi/kol-icons": "^0.25.0",
30
+ "@kolkrabbi/kol-framework": "^0.36.0",
31
+ "@kolkrabbi/kol-theme": "^0.112.0"
32
32
  },
33
33
  "files": [
34
34
  "src",
@@ -5,11 +5,12 @@ import TagPath from './TagPath.jsx'
5
5
  import { useTagMode } from './TagModeContext.jsx'
6
6
  import TagGraph from './TagGraph.jsx'
7
7
  import RailRow from '../shell/RailRow.jsx'
8
- import { extractDocNumber, cleanTitle } from '../engine/index.js'
8
+ import { extractDocNumber, cleanTitle, matchSearchItems } from '../engine/index.js'
9
9
  import { buildTagCounts } from '../engine/tags.js'
10
10
 
11
11
  /**
12
- * TagModeOverlay - the tag BROWSER: the list and the graph. It does not search.
12
+ * TagModeOverlay - the committed RESULTS view: documents that match, with tags
13
+ * as a facet over them. It was a tag browser that forgot the query.
13
14
  *
14
15
  * ONE SEARCH (user ruling 2026-08-01: "search SHOULD be ONE system not 2").
15
16
  * This component owned a second one - a raw `Input` plus
@@ -21,27 +22,62 @@ import { buildTagCounts } from '../engine/tags.js'
21
22
  *
22
23
  * `view` comes from the CONTEXT, not local state - the rail's "Graph view" row
23
24
  * has to say which mode it wants before this component exists.
25
+ *
26
+ * IT NOW READS `text` (TagModeOverlayIgnoresQuery, kol-website 2026-08-31).
27
+ * Pressing return set `expanded`, ShellLayout swapped its body for this
28
+ * component, and this component consulted `activeTags` and nothing else — so a
29
+ * typed query was replaced by the COMPLETE UNFILTERED TAG CENSUS and zero
30
+ * document rows, by construction. Searching `rf` listed
31
+ * `project/kol-monorepo 85`, `domain/design-system 13` … not one of which
32
+ * contains `rf`. The engine was never the problem: `matchSearchItems` already
33
+ * spans label / tags / headings / keywords, and ShellLayout was computing
34
+ * exactly that. The expanded view simply threw it away.
35
+ *
36
+ * ONE MATCHER (the 2026-08-01 ruling, still): this reuses `matchSearchItems`
37
+ * rather than adding a second predicate, so committed and uncommitted results
38
+ * rank identically. `text` and `activeTags` are two facets of one query.
24
39
  */
25
40
  const TagModeOverlay = () => {
26
- const { activeTags, activeTag, toggleTag, clearTags, closeTagMode, inventory, docHref, tagHref, view, setView } = useTagMode()
41
+ const { activeTags, activeTag, toggleTag, clearTags, closeTagMode, inventory, docHref, tagHref, view, setView, text } = useTagMode()
42
+
43
+ const query = (text || '').trim()
27
44
 
28
- const allTagsWithCount = useMemo(() => buildTagCounts(inventory), [inventory])
45
+ /* THE TWO FACETS, IN ORDER: chips narrow first, then the query matches over
46
+ * what is left. Either alone is a filter; neither is required. */
47
+ const filteredDocs = useMemo(() => {
48
+ const tagged = activeTags.length === 0
49
+ ? inventory
50
+ : inventory.filter((d) => Array.isArray(d.metadata?.tags) && activeTags.every((t) => d.metadata.tags.includes(t)))
51
+ if (!query) return activeTags.length === 0 ? [] : tagged
52
+ /* the engine's shape, not a second matcher */
53
+ const matched = matchSearchItems(
54
+ tagged.map((d) => ({
55
+ ...d,
56
+ label: cleanTitle(d.title, d.id),
57
+ tags: d.metadata?.tags ?? [],
58
+ headings: d.headings ?? d.metadata?.headings ?? [],
59
+ keywords: d.keywords ?? d.metadata?.keywords ?? [],
60
+ })),
61
+ query,
62
+ )
63
+ return matched
64
+ }, [inventory, activeTags, query])
65
+
66
+ const hasFilters = activeTags.length > 0 || query.length > 0
67
+
68
+ /* THE CLOUD NARROWS TO THE RESULT SET, with counts recomputed over it. An
69
+ * empty query keeps the full census — that is a good browse state, and it is
70
+ * only wrong once a query exists. */
71
+ const allTagsWithCount = useMemo(
72
+ () => buildTagCounts(hasFilters ? filteredDocs : inventory),
73
+ [inventory, filteredDocs, hasFilters],
74
+ )
29
75
 
30
76
  const visibleTags = useMemo(
31
77
  () => allTagsWithCount.filter(({ tag }) => !activeTags.includes(tag)),
32
78
  [allTagsWithCount, activeTags]
33
79
  )
34
80
 
35
- const filteredDocs = useMemo(() => {
36
- if (activeTags.length === 0) return []
37
- return inventory.filter((d) => {
38
- if (!Array.isArray(d.metadata?.tags)) return false
39
- return activeTags.every((t) => d.metadata.tags.includes(t))
40
- })
41
- }, [inventory, activeTags])
42
-
43
- const hasFilters = activeTags.length > 0
44
-
45
81
  return (
46
82
  /* NOT AN OVERLAY ANY MORE (user ruling 2026-08-01). This is the EXPANDED
47
83
  * BODY of ShellSearchOverlay, rendered inside its panel — so it owns no
@@ -115,6 +151,13 @@ const TagModeOverlay = () => {
115
151
  )}
116
152
  </div>
117
153
 
154
+ {/* A QUERY THAT MATCHES NOTHING SAYS SO rather than falling
155
+ * back to the census — the census was the bug. */}
156
+ {hasFilters && filteredDocs.length === 0 && (
157
+ <p className="text-fg-48 kol-mono-12 py-4 border-t border-fg-08">
158
+ No documents match{query ? ` “${query}”` : ''}
159
+ </p>
160
+ )}
118
161
  {hasFilters && filteredDocs.length > 0 && (
119
162
  <div className="shell-nav-items pt-4 border-t border-fg-08">
120
163
  {filteredDocs.map((d) => (