shelving 1.245.0 → 1.246.1

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.
@@ -32,6 +32,7 @@ export interface PackageExtractorOptions {
32
32
  * - Static export keys (e.g. `"./api"`, `"./firestore/client"`) become one module each.
33
33
  * - Wildcard export keys (e.g. `"./util/*"`) expand against the source tree — one module per matching child file or subdirectory.
34
34
  * - Each export's *target* extension (e.g. the `.js` in `"./util/*.js"`) is mapped to source extensions via `extensions`, so built `.js` paths resolve to their `.ts` sources.
35
+ * - Each module's `title` is prefixed with the package `name` (e.g. `ui` → `shelving/ui`) so listings read as package subpaths.
35
36
  * - The `"."` root export is skipped — its content is the root tree element itself.
36
37
  * - Throws if a static export key has no matching source element in the tree.
37
38
  *
@@ -16,6 +16,7 @@ const DEFAULT_EXTENSIONS = {
16
16
  * - Static export keys (e.g. `"./api"`, `"./firestore/client"`) become one module each.
17
17
  * - Wildcard export keys (e.g. `"./util/*"`) expand against the source tree — one module per matching child file or subdirectory.
18
18
  * - Each export's *target* extension (e.g. the `.js` in `"./util/*.js"`) is mapped to source extensions via `extensions`, so built `.js` paths resolve to their `.ts` sources.
19
+ * - Each module's `title` is prefixed with the package `name` (e.g. `ui` → `shelving/ui`) so listings read as package subpaths.
19
20
  * - The `"."` root export is skipped — its content is the root tree element itself.
20
21
  * - Throws if a static export key has no matching source element in the tree.
21
22
  *
@@ -56,7 +57,9 @@ export class PackageExtractor extends Extractor {
56
57
  async extract(packageJson) {
57
58
  const pkgPath = requirePath(packageJson, this._base, this.extract);
58
59
  const pkg = (await Bun.file(pkgPath).json());
59
- const exports = pkg.exports ?? {};
60
+ const tree = this._tree;
61
+ // Read the package name alongside its exports — the name prefixes each module title (e.g. `ui` → `shelving/ui`).
62
+ const { name, exports = {} } = pkg;
60
63
  const modules = [];
61
64
  for (const [key, value] of Object.entries(exports)) {
62
65
  if (key === ".")
@@ -76,18 +79,21 @@ export class PackageExtractor extends Extractor {
76
79
  modules.push(this._module.extract({ name: subpath, source }));
77
80
  }
78
81
  }
79
- const tree = this._tree;
82
+ // Prefix the package name onto each module's title so listings read `shelving/ui` rather than a bare `ui`, making modules glanceable as package subpaths.
83
+ const children = name
84
+ ? modules.map(module => ({ ...module, props: { ...module.props, title: `${name}/${module.props.title ?? module.props.name}` } }))
85
+ : modules;
80
86
  // Canonical URL `path`s aren't stamped here — they're derived from tree structure when the tree is flattened (`flattenTree()`) in the UI layer.
81
87
  return {
82
88
  type: "tree-element",
83
- key: pkg.name ?? tree.key,
89
+ key: name ?? tree.key,
84
90
  props: {
85
91
  source: tree.props.source,
86
- name: pkg.name ?? tree.props.name,
87
- title: pkg.name ?? tree.props.title,
92
+ name: name ?? tree.props.name,
93
+ title: name ?? tree.props.title,
88
94
  description: pkg.description ?? tree.props.description,
89
95
  content: tree.props.content,
90
- children: modules,
96
+ children,
91
97
  },
92
98
  };
93
99
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shelving",
3
- "version": "1.245.0",
3
+ "version": "1.246.1",
4
4
  "author": "Dave Houlbrooke <dave@shax.com>",
5
5
  "repository": {
6
6
  "type": "git",
package/util/tree.d.ts CHANGED
@@ -185,8 +185,9 @@ export interface SearchTreeOptions {
185
185
  * Search the descendants of a tree element and return the best-ranked matches.
186
186
  *
187
187
  * - Walks every descendant of `scope` (depth-first; `scope` itself is not a candidate), optionally narrowed by `options.filter`.
188
- * - Tokenises `query` with `getWords()` so quoted phrases match literally: `searchTree(root, '"hello world" foo')` scores the phrase `hello world` *and* the word `foo` independently, stacking their scores.
189
- * - Ranks each candidate (case-insensitive) per token, summing: `name` exact > `name` starts-with > `name` includes > `title` includes > `description` includes > `content` includes. A `name` match always outranks a content-only match.
188
+ * - Tokenises `query` with `getWords()` so quoted phrases match literally: `searchTree(root, '"hello world" foo')` matches the phrase `hello world` *and* the word `foo`.
189
+ * - Requires *every* token to match (AND, not OR): a candidate is only returned when each token matches at least one of its props. So `date util` returns only elements matching both `date` and `util`, not either alone.
190
+ * - Ranks each candidate (case-insensitive) per token, summing each token's best tier: `name` exact > `name` starts-with > `name` includes > `title` includes > `description` includes > `content` includes. A `name` match always outranks a content-only match.
190
191
  * - An empty `query` returns the (filtered) candidates in tree order — useful for a filter-only or "show everything" listing.
191
192
  *
192
193
  * @param scope The element whose descendants are searched.
package/util/tree.js CHANGED
@@ -47,8 +47,9 @@ function _flatKey(element) {
47
47
  * Search the descendants of a tree element and return the best-ranked matches.
48
48
  *
49
49
  * - Walks every descendant of `scope` (depth-first; `scope` itself is not a candidate), optionally narrowed by `options.filter`.
50
- * - Tokenises `query` with `getWords()` so quoted phrases match literally: `searchTree(root, '"hello world" foo')` scores the phrase `hello world` *and* the word `foo` independently, stacking their scores.
51
- * - Ranks each candidate (case-insensitive) per token, summing: `name` exact > `name` starts-with > `name` includes > `title` includes > `description` includes > `content` includes. A `name` match always outranks a content-only match.
50
+ * - Tokenises `query` with `getWords()` so quoted phrases match literally: `searchTree(root, '"hello world" foo')` matches the phrase `hello world` *and* the word `foo`.
51
+ * - Requires *every* token to match (AND, not OR): a candidate is only returned when each token matches at least one of its props. So `date util` returns only elements matching both `date` and `util`, not either alone.
52
+ * - Ranks each candidate (case-insensitive) per token, summing each token's best tier: `name` exact > `name` starts-with > `name` includes > `title` includes > `description` includes > `content` includes. A `name` match always outranks a content-only match.
52
53
  * - An empty `query` returns the (filtered) candidates in tree order — useful for a filter-only or "show everything" listing.
53
54
  *
54
55
  * @param scope The element whose descendants are searched.
@@ -91,7 +92,7 @@ const _SCORE_NAME_INCLUDES = 100;
91
92
  const _SCORE_TITLE = 10;
92
93
  const _SCORE_DESCRIPTION = 4;
93
94
  const _SCORE_CONTENT = 1;
94
- /** Score one element's props against the (already lower-cased) query tokens — each token contributes its best-matching tier, summed. */
95
+ /** Score one element's props against the (already lower-cased) query tokens — every token must match (AND), each contributing its best-matching tier, summed. */
95
96
  function _scoreElement(props, tokens) {
96
97
  const name = props.name.toLowerCase();
97
98
  const title = props.title?.toLowerCase() ?? "";
@@ -99,6 +100,7 @@ function _scoreElement(props, tokens) {
99
100
  const content = props.content?.toLowerCase() ?? "";
100
101
  let score = 0;
101
102
  for (const token of tokens) {
103
+ // Every token must match somewhere — a single token that matches nothing drops the candidate entirely (AND, not OR).
102
104
  if (name === token)
103
105
  score += _SCORE_NAME_EXACT;
104
106
  else if (name.startsWith(token))
@@ -111,6 +113,8 @@ function _scoreElement(props, tokens) {
111
113
  score += _SCORE_DESCRIPTION;
112
114
  else if (content.includes(token))
113
115
  score += _SCORE_CONTENT;
116
+ else
117
+ return 0;
114
118
  }
115
119
  return score;
116
120
  }