handoff-app 1.1.3-0 → 1.1.3-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.
package/Changelog.md CHANGED
@@ -41,6 +41,41 @@ block styling, and GitHub Flavored Markdown table support.
41
41
  autolinks, and task lists now render correctly. Tables are styled via Tailwind
42
42
  Typography's `.prose table` defaults.
43
43
 
44
+ ### Bug Fixes
45
+
46
+ - **New components not detected in dev mode** — `npm run start` now watches
47
+ the parent component directories (from `config.entries.components`) for
48
+ newly created components. Previously, only components known at startup had
49
+ their files watched; adding a new component directory required restarting
50
+ the dev server. A new `watchComponentDirectories` watcher detects when a
51
+ config file (`.json` / `.js` / `.cjs`) appears in a new subdirectory,
52
+ reloads the runtime config, restarts the component and configuration
53
+ watchers, and processes the new component automatically.
54
+
55
+ - **Heading anchor IDs truncated** — The markdown heading anchor generator
56
+ was only reading the first child node (`children[0]`) instead of the full
57
+ heading text, producing single-character or partial IDs (e.g. `id="u"`
58
+ instead of `id="using-icons-in-templates"`). A new `extractText` helper
59
+ recursively walks all children to build the complete anchor slug.
60
+
61
+ - **Fenced code blocks without a language unstyled** — Bare ```` ``` ````
62
+ blocks (no language identifier) now receive the same styled treatment as
63
+ language-tagged blocks: toolbar, copy button, and themed background.
64
+ Inline code is unaffected.
65
+
66
+ - **Code blocks pushing page width** — Added `min-w-0` and `max-w-full` on
67
+ the code block wrapper and the grid content column to prevent wide code
68
+ from stretching the layout and pushing the TOC off-screen.
69
+
70
+ - **Frontmatter menu duplicated by auto-scan** — When a markdown page
71
+ defines its sidebar menu via frontmatter `menu`, the automatic directory
72
+ scanner no longer appends duplicate entries. Auto-scanning now only runs
73
+ as a fallback when no frontmatter menu is present.
74
+
75
+ - **`.prose` max-width too narrow** — Overrode Tailwind Typography's default
76
+ `max-width: 65ch` on `.prose` to `100%` so markdown content fills the
77
+ available container width.
78
+
44
79
  ### Changed
45
80
 
46
81
  - Deleted `src/app/pages/[level1]/index.tsx` and
@@ -49,6 +84,8 @@ block styling, and GitHub Flavored Markdown table support.
49
84
  interface from `src/app/components/util/index.ts`.
50
85
  - `ComponentGuidelines` now uses `MarkdownComponents` and the remark plugins
51
86
  for consistent rendering.
87
+ - Added `system/tokens/**` paths to `knownPaths` to prevent catch-all route
88
+ conflicts with dedicated token pages.
52
89
 
53
90
  ## \[1.1.2] - 2026-03-26
54
91
 
@@ -175,10 +175,12 @@ const watchApp = (handoff) => __awaiter(void 0, void 0, void 0, function* () {
175
175
  debounce: false,
176
176
  runtimeComponentsWatcher: null,
177
177
  runtimeConfigurationWatcher: null,
178
+ componentDirectoriesWatcher: null,
178
179
  };
179
180
  (0, watchers_1.watchPublicDirectory)(handoff, wss, state, chokidarConfig);
180
181
  (0, watchers_1.watchRuntimeComponents)(handoff, state, (0, watchers_1.getRuntimeComponentsPathsToWatch)(handoff));
181
182
  (0, watchers_1.watchRuntimeConfiguration)(handoff, state);
183
+ (0, watchers_1.watchComponentDirectories)(handoff, state, chokidarConfig);
182
184
  (0, watchers_1.watchGlobalEntries)(handoff, state, chokidarConfig);
183
185
  (0, watchers_1.watchPages)(handoff, chokidarConfig);
184
186
  });
@@ -6,6 +6,7 @@ export interface WatcherState {
6
6
  debounce: boolean;
7
7
  runtimeComponentsWatcher: chokidar.FSWatcher | null;
8
8
  runtimeConfigurationWatcher: chokidar.FSWatcher | null;
9
+ componentDirectoriesWatcher: chokidar.FSWatcher | null;
9
10
  }
10
11
  /**
11
12
  * Watches the working public directory for changes and updates the app.
@@ -34,7 +35,7 @@ export declare const mapEntryTypeToSegment: (type: keyof ComponentListObject["en
34
35
  *
35
36
  * @returns A Map of paths to watch and their entry types
36
37
  */
37
- export declare const getRuntimeComponentsPathsToWatch: (handoff: Handoff) => Map<string, "template" | "js" | "scss">;
38
+ export declare const getRuntimeComponentsPathsToWatch: (handoff: Handoff) => Map<string, "js" | "scss" | "template">;
38
39
  /**
39
40
  * Watches runtime components for changes.
40
41
  */
@@ -43,3 +44,10 @@ export declare const watchRuntimeComponents: (handoff: Handoff, state: WatcherSt
43
44
  * Watches the runtime configuration for changes.
44
45
  */
45
46
  export declare const watchRuntimeConfiguration: (handoff: Handoff, state: WatcherState) => void;
47
+ /**
48
+ * Watches the parent component directories from config.entries.components
49
+ * for new components being added. When a new config file (e.g. button.json)
50
+ * appears in a new subdirectory, reloads the runtime config and restarts
51
+ * the component/configuration watchers so the new component is picked up.
52
+ */
53
+ export declare const watchComponentDirectories: (handoff: Handoff, state: WatcherState, chokidarConfig: chokidar.WatchOptions) => void;
@@ -35,7 +35,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
35
35
  return (mod && mod.__esModule) ? mod : { "default": mod };
36
36
  };
37
37
  Object.defineProperty(exports, "__esModule", { value: true });
38
- exports.watchRuntimeConfiguration = exports.watchRuntimeComponents = exports.getRuntimeComponentsPathsToWatch = exports.mapEntryTypeToSegment = exports.watchGlobalEntries = exports.watchPages = exports.watchAppSource = exports.watchPublicDirectory = void 0;
38
+ exports.watchComponentDirectories = exports.watchRuntimeConfiguration = exports.watchRuntimeComponents = exports.getRuntimeComponentsPathsToWatch = exports.mapEntryTypeToSegment = exports.watchGlobalEntries = exports.watchPages = exports.watchAppSource = exports.watchPublicDirectory = void 0;
39
39
  const chokidar_1 = __importDefault(require("chokidar"));
40
40
  const fs_extra_1 = __importDefault(require("fs-extra"));
41
41
  const path_1 = __importDefault(require("path"));
@@ -277,13 +277,9 @@ const watchRuntimeConfiguration = (handoff, state) => {
277
277
  state.debounce = true;
278
278
  try {
279
279
  file = path_1.default.dirname(file);
280
- // Reload the Handoff instance to pick up configuration changes
281
280
  handoff.reload();
282
- // After reloading, persist the updated client configuration
283
281
  yield (0, client_config_1.persistClientConfig)(handoff);
284
- // Restart the runtime components watcher to track potentially updated/added/removed components
285
282
  (0, exports.watchRuntimeComponents)(handoff, state, (0, exports.getRuntimeComponentsPathsToWatch)(handoff));
286
- // Process components based on the updated configuration and file path
287
283
  yield (0, builder_1.default)(handoff, path_1.default.basename(file));
288
284
  }
289
285
  catch (e) {
@@ -299,3 +295,61 @@ const watchRuntimeConfiguration = (handoff, state) => {
299
295
  }
300
296
  };
301
297
  exports.watchRuntimeConfiguration = watchRuntimeConfiguration;
298
+ /**
299
+ * Watches the parent component directories from config.entries.components
300
+ * for new components being added. When a new config file (e.g. button.json)
301
+ * appears in a new subdirectory, reloads the runtime config and restarts
302
+ * the component/configuration watchers so the new component is picked up.
303
+ */
304
+ const watchComponentDirectories = (handoff, state, chokidarConfig) => {
305
+ var _a, _b, _c, _d, _e;
306
+ if (state.componentDirectoriesWatcher) {
307
+ state.componentDirectoriesWatcher.close();
308
+ }
309
+ const componentPaths = (_b = (_a = handoff.config.entries) === null || _a === void 0 ? void 0 : _a.components) !== null && _b !== void 0 ? _b : [];
310
+ if (componentPaths.length === 0)
311
+ return;
312
+ const dirsToWatch = [];
313
+ for (const componentPath of componentPaths) {
314
+ const resolved = path_1.default.resolve(handoff.workingPath, componentPath);
315
+ if (fs_extra_1.default.existsSync(resolved) && fs_extra_1.default.statSync(resolved).isDirectory()) {
316
+ dirsToWatch.push(resolved);
317
+ }
318
+ }
319
+ if (dirsToWatch.length === 0)
320
+ return;
321
+ const knownComponents = new Set(Object.keys((_e = (_d = (_c = handoff.runtimeConfig) === null || _c === void 0 ? void 0 : _c.entries) === null || _d === void 0 ? void 0 : _d.components) !== null && _e !== void 0 ? _e : {}));
322
+ state.componentDirectoriesWatcher = chokidar_1.default.watch(dirsToWatch, Object.assign(Object.assign({}, chokidarConfig), { depth: 1 }));
323
+ state.componentDirectoriesWatcher.on('add', (file) => __awaiter(void 0, void 0, void 0, function* () {
324
+ var _a, _b, _c;
325
+ const basename = path_1.default.basename(file);
326
+ const dirName = path_1.default.basename(path_1.default.dirname(file));
327
+ const isConfigFile = basename.endsWith('.json') || basename.endsWith('.js') || basename.endsWith('.cjs');
328
+ const isNewComponent = isConfigFile && basename.startsWith(dirName) && !knownComponents.has(dirName);
329
+ if (!isNewComponent)
330
+ return;
331
+ if (!state.debounce) {
332
+ state.debounce = true;
333
+ try {
334
+ logger_1.Logger.warn(`New component detected: ${dirName}. Reloading configuration...`);
335
+ handoff.reload();
336
+ knownComponents.add(dirName);
337
+ // Update known set with all current components
338
+ for (const id of Object.keys((_c = (_b = (_a = handoff.runtimeConfig) === null || _a === void 0 ? void 0 : _a.entries) === null || _b === void 0 ? void 0 : _b.components) !== null && _c !== void 0 ? _c : {})) {
339
+ knownComponents.add(id);
340
+ }
341
+ yield (0, client_config_1.persistClientConfig)(handoff);
342
+ (0, exports.watchRuntimeComponents)(handoff, state, (0, exports.getRuntimeComponentsPathsToWatch)(handoff));
343
+ (0, exports.watchRuntimeConfiguration)(handoff, state);
344
+ yield (0, builder_1.default)(handoff, dirName);
345
+ }
346
+ catch (e) {
347
+ logger_1.Logger.error('Error processing new component:', e);
348
+ }
349
+ finally {
350
+ state.debounce = false;
351
+ }
352
+ }
353
+ }));
354
+ };
355
+ exports.watchComponentDirectories = watchComponentDirectories;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "handoff-app",
3
- "version": "1.1.3-0",
3
+ "version": "1.1.3-1",
4
4
  "description": "Automated documentation toolchain for building client side documentation from figma",
5
5
  "author ": {
6
6
  "name": "Convertiv",
@@ -92,6 +92,12 @@ export const knownPaths = [
92
92
  'foundations/typography',
93
93
  'system',
94
94
  'system/component',
95
+ 'system/tokens',
96
+ 'system/tokens/foundations',
97
+ 'system/tokens/foundations/colors',
98
+ 'system/tokens/foundations/effects',
99
+ 'system/tokens/foundations/typography',
100
+ 'system/tokens/components',
95
101
  ];
96
102
 
97
103
  /**
@@ -9,7 +9,7 @@ import processComponents from '../transformers/preview/component/builder';
9
9
  import { Logger } from '../utils/logger';
10
10
  import { generateTokensApi, persistClientConfig } from './client-config';
11
11
  import { getAppPath, syncPublicFiles } from './paths';
12
- import { WatcherState, getRuntimeComponentsPathsToWatch, watchAppSource, watchGlobalEntries, watchPages, watchPublicDirectory, watchRuntimeComponents, watchRuntimeConfiguration } from './watchers';
12
+ import { WatcherState, getRuntimeComponentsPathsToWatch, watchAppSource, watchComponentDirectories, watchGlobalEntries, watchPages, watchPublicDirectory, watchRuntimeComponents, watchRuntimeConfiguration } from './watchers';
13
13
  import { createWebSocketServer } from './websocket';
14
14
 
15
15
  const escapeForSingleQuotedJsString = (value: string): string => value.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
@@ -193,11 +193,13 @@ export const watchApp = async (handoff: Handoff): Promise<void> => {
193
193
  debounce: false,
194
194
  runtimeComponentsWatcher: null,
195
195
  runtimeConfigurationWatcher: null,
196
+ componentDirectoriesWatcher: null,
196
197
  };
197
198
 
198
199
  watchPublicDirectory(handoff, wss, state, chokidarConfig);
199
200
  watchRuntimeComponents(handoff, state, getRuntimeComponentsPathsToWatch(handoff));
200
201
  watchRuntimeConfiguration(handoff, state);
202
+ watchComponentDirectories(handoff, state, chokidarConfig);
201
203
  watchGlobalEntries(handoff, state, chokidarConfig);
202
204
  watchPages(handoff, chokidarConfig);
203
205
  };
@@ -15,6 +15,7 @@ export interface WatcherState {
15
15
  debounce: boolean;
16
16
  runtimeComponentsWatcher: chokidar.FSWatcher | null;
17
17
  runtimeConfigurationWatcher: chokidar.FSWatcher | null;
18
+ componentDirectoriesWatcher: chokidar.FSWatcher | null;
18
19
  }
19
20
 
20
21
  /**
@@ -258,13 +259,9 @@ export const watchRuntimeConfiguration = (handoff: Handoff, state: WatcherState)
258
259
  state.debounce = true;
259
260
  try {
260
261
  file = path.dirname(file);
261
- // Reload the Handoff instance to pick up configuration changes
262
262
  handoff.reload();
263
- // After reloading, persist the updated client configuration
264
263
  await persistClientConfig(handoff);
265
- // Restart the runtime components watcher to track potentially updated/added/removed components
266
264
  watchRuntimeComponents(handoff, state, getRuntimeComponentsPathsToWatch(handoff));
267
- // Process components based on the updated configuration and file path
268
265
  await processComponents(handoff, path.basename(file));
269
266
  } catch (e) {
270
267
  Logger.error('Error reloading runtime configuration:', e);
@@ -277,3 +274,68 @@ export const watchRuntimeConfiguration = (handoff: Handoff, state: WatcherState)
277
274
  });
278
275
  }
279
276
  };
277
+
278
+ /**
279
+ * Watches the parent component directories from config.entries.components
280
+ * for new components being added. When a new config file (e.g. button.json)
281
+ * appears in a new subdirectory, reloads the runtime config and restarts
282
+ * the component/configuration watchers so the new component is picked up.
283
+ */
284
+ export const watchComponentDirectories = (handoff: Handoff, state: WatcherState, chokidarConfig: chokidar.WatchOptions) => {
285
+ if (state.componentDirectoriesWatcher) {
286
+ state.componentDirectoriesWatcher.close();
287
+ }
288
+
289
+ const componentPaths = handoff.config.entries?.components ?? [];
290
+ if (componentPaths.length === 0) return;
291
+
292
+ const dirsToWatch: string[] = [];
293
+ for (const componentPath of componentPaths) {
294
+ const resolved = path.resolve(handoff.workingPath, componentPath);
295
+ if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) {
296
+ dirsToWatch.push(resolved);
297
+ }
298
+ }
299
+
300
+ if (dirsToWatch.length === 0) return;
301
+
302
+ const knownComponents = new Set(Object.keys(handoff.runtimeConfig?.entries?.components ?? {}));
303
+
304
+ state.componentDirectoriesWatcher = chokidar.watch(dirsToWatch, {
305
+ ...chokidarConfig,
306
+ depth: 1,
307
+ });
308
+
309
+ state.componentDirectoriesWatcher.on('add', async (file) => {
310
+ const basename = path.basename(file);
311
+ const dirName = path.basename(path.dirname(file));
312
+
313
+ const isConfigFile = basename.endsWith('.json') || basename.endsWith('.js') || basename.endsWith('.cjs');
314
+ const isNewComponent = isConfigFile && basename.startsWith(dirName) && !knownComponents.has(dirName);
315
+
316
+ if (!isNewComponent) return;
317
+
318
+ if (!state.debounce) {
319
+ state.debounce = true;
320
+ try {
321
+ Logger.warn(`New component detected: ${dirName}. Reloading configuration...`);
322
+ handoff.reload();
323
+ knownComponents.add(dirName);
324
+
325
+ // Update known set with all current components
326
+ for (const id of Object.keys(handoff.runtimeConfig?.entries?.components ?? {})) {
327
+ knownComponents.add(id);
328
+ }
329
+
330
+ await persistClientConfig(handoff);
331
+ watchRuntimeComponents(handoff, state, getRuntimeComponentsPathsToWatch(handoff));
332
+ watchRuntimeConfiguration(handoff, state);
333
+ await processComponents(handoff, dirName);
334
+ } catch (e) {
335
+ Logger.error('Error processing new component:', e);
336
+ } finally {
337
+ state.debounce = false;
338
+ }
339
+ }
340
+ });
341
+ };