@olomonhelpdesk2/helpdesk 0.1.17 → 0.1.19

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/helpdesk-seed.mjs CHANGED
@@ -1001,8 +1001,14 @@ var CAPTURE_CONTRACT = {
1001
1001
  timezone: "UTC",
1002
1002
  /**
1003
1003
  * Injected before every shot. Necessary, not sufficient — see above.
1004
+ *
1005
+ * Every CSS animation ends at once, on its last frame, rather than being
1006
+ * removed. An entrance starts its element at opacity 0 and keeps the last
1007
+ * frame it animates to, and `animation: none` left that element at opacity
1008
+ * 0, so a site's hero was blank in every frame. One that loops ends after a
1009
+ * single instant pass, back at its element's own style.
1004
1010
  */
1005
- motionOff: "*,*::before,*::after{animation:none!important;transition:none!important;caret-color:transparent!important}",
1011
+ motionOff: "*,*::before,*::after{animation-duration:0s!important;animation-delay:0s!important;animation-iteration-count:1!important;transition:none!important;caret-color:transparent!important}",
1006
1012
  /**
1007
1013
  * What an app uses to leave its own floating widgets out of the pictures.
1008
1014
  *
package/helpdesk.mjs CHANGED
@@ -148,6 +148,34 @@ function depth(path) {
148
148
  return path === "/" ? 0 : path.split("/").filter(Boolean).length;
149
149
  }
150
150
 
151
+ // core/discovery/apps.ts
152
+ var ROOT_SHARE = 0.8;
153
+ function appRoot(routes) {
154
+ const screens = documentable(routes);
155
+ const root = [];
156
+ for (; ; ) {
157
+ const counts = /* @__PURE__ */ new Map();
158
+ for (const route of screens) {
159
+ const segments = staticSegments(route.path);
160
+ const next = segments[root.length];
161
+ if (next && root.every((segment2, index) => segments[index] === segment2)) counts.set(next, (counts.get(next) ?? 0) + 1);
162
+ }
163
+ const [segment, count = 0] = [...counts].sort((a, b) => b[1] - a[1])[0] ?? [];
164
+ if (!segment || count < Math.max(3, screens.length * ROOT_SHARE)) return root;
165
+ root.push(segment);
166
+ }
167
+ }
168
+ function belowAppRoot(path, root) {
169
+ const segments = path.split("/").filter(Boolean);
170
+ return root.length && root.every((segment, index) => segments[index] === segment) ? segments.slice(root.length) : segments;
171
+ }
172
+ function staticSegments(path) {
173
+ return path.split("/").filter((segment) => segment && !segment.startsWith("["));
174
+ }
175
+ function documentable(routes) {
176
+ return routes.filter((route) => !route.skipped);
177
+ }
178
+
151
179
  // core/discovery/article-plan.ts
152
180
  var MONEY_OR_ACCESS = /* @__PURE__ */ new Set([
153
181
  "billing",
@@ -226,9 +254,9 @@ function slugFor(path) {
226
254
  const segments = path.split("/").filter(Boolean).filter((segment) => !segment.startsWith("["));
227
255
  return segments.length ? segments.join("/") : "overview";
228
256
  }
229
- function titleFor(route) {
230
- const segments = route.path.split("/").filter(Boolean).filter((s) => !s.startsWith("["));
231
- if (!segments.length) return "Getting around";
257
+ function titleFor(route, root) {
258
+ const segments = belowAppRoot(route.path, root).filter((s) => !s.startsWith("["));
259
+ if (!segments.length) return route.params.length ? "Working with a record" : "Getting around";
232
260
  const words = segments[segments.length - 1].replace(/[-_]/g, " ");
233
261
  const subject = words.charAt(0).toUpperCase() + words.slice(1);
234
262
  if (!route.params.length) return subject;
@@ -280,6 +308,7 @@ function buildArticlePlan(options) {
280
308
  }
281
309
  }
282
310
  let covered = 0;
311
+ const root = appRoot(options.routes);
283
312
  const needs = [...merged.entries()].map(([slug, group], index) => {
284
313
  const rank = index + 1;
285
314
  const claimed = [group.entry.route.path, ...group.routes.slice(1) ?? []].map((path) => options.documented?.[path]).find(Boolean);
@@ -287,7 +316,7 @@ function buildArticlePlan(options) {
287
316
  if (already) covered += 1;
288
317
  return {
289
318
  slug,
290
- title: group.title ?? titleFor(group.entry.route),
319
+ title: group.title ?? titleFor(group.entry.route, root),
291
320
  route: group.entry.route.path,
292
321
  ...group.routes.length > 1 ? { alsoCovers: group.routes.slice(1) } : {},
293
322
  tier: tierFor(rank, merged.size),
@@ -951,8 +980,8 @@ function readProduct(routes, options) {
951
980
  }
952
981
  return found;
953
982
  };
954
- const documentable = routes.filter((route) => !route.skipped);
955
- const total = Math.min(documentable.length, maxScreens);
983
+ const documentable2 = routes.filter((route) => !route.skipped);
984
+ const total = Math.min(documentable2.length, maxScreens);
956
985
  const reading = { how: "source", commit: options.commit ?? null };
957
986
  let done = 0;
958
987
  let withFacts = 0;
@@ -984,10 +1013,10 @@ function hasAnything(facts) {
984
1013
  );
985
1014
  }
986
1015
  function describeReading(routes) {
987
- const documentable = routes.filter((route) => !route.skipped);
988
- const read = documentable.filter((route) => route.facts && hasAnything(route.facts));
1016
+ const documentable2 = routes.filter((route) => !route.skipped);
1017
+ const read = documentable2.filter((route) => route.facts && hasAnything(route.facts));
989
1018
  return {
990
- screens: documentable.length,
1019
+ screens: documentable2.length,
991
1020
  read: read.length,
992
1021
  rendered: read.filter((route) => route.facts?.read?.how === "rendered").length,
993
1022
  pathsOnly: read.length === 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olomonhelpdesk2/helpdesk",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "description": "Reads a product's screens, plans its help articles, and photographs them.",
5
5
  "type": "module",
6
6
  "engines": {