@deskwork/studio 0.15.0 → 0.16.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.
Files changed (71) hide show
  1. package/dist/components/scrapbook-item.d.ts +20 -0
  2. package/dist/components/scrapbook-item.d.ts.map +1 -1
  3. package/dist/components/scrapbook-item.js +21 -2
  4. package/dist/components/scrapbook-item.js.map +1 -1
  5. package/dist/pages/dashboard/affordances.d.ts.map +1 -1
  6. package/dist/pages/dashboard/affordances.js +13 -1
  7. package/dist/pages/dashboard/affordances.js.map +1 -1
  8. package/dist/pages/dashboard/affordances.ts +13 -1
  9. package/dist/pages/review-scrapbook-drawer.d.ts.map +1 -1
  10. package/dist/pages/review-scrapbook-drawer.js +10 -1
  11. package/dist/pages/review-scrapbook-drawer.js.map +1 -1
  12. package/dist/pages/review-scrapbook-drawer.ts +11 -1
  13. package/dist/pages/scrapbook/dispatch.d.ts +50 -0
  14. package/dist/pages/scrapbook/dispatch.d.ts.map +1 -0
  15. package/dist/pages/scrapbook/dispatch.js +104 -0
  16. package/dist/pages/scrapbook/dispatch.js.map +1 -0
  17. package/dist/pages/scrapbook/image-readers.d.ts +24 -0
  18. package/dist/pages/scrapbook/image-readers.d.ts.map +1 -0
  19. package/dist/pages/scrapbook/image-readers.js +135 -0
  20. package/dist/pages/scrapbook/image-readers.js.map +1 -0
  21. package/dist/pages/scrapbook/index.d.ts +21 -0
  22. package/dist/pages/scrapbook/index.d.ts.map +1 -0
  23. package/dist/pages/scrapbook/index.js +96 -0
  24. package/dist/pages/scrapbook/index.js.map +1 -0
  25. package/dist/pages/scrapbook/render.d.ts +68 -0
  26. package/dist/pages/scrapbook/render.d.ts.map +1 -0
  27. package/dist/pages/scrapbook/render.js +315 -0
  28. package/dist/pages/scrapbook/render.js.map +1 -0
  29. package/dist/pages/scrapbook/text-helpers.d.ts +40 -0
  30. package/dist/pages/scrapbook/text-helpers.d.ts.map +1 -0
  31. package/dist/pages/scrapbook/text-helpers.js +92 -0
  32. package/dist/pages/scrapbook/text-helpers.js.map +1 -0
  33. package/dist/pages/scrapbook/types.d.ts +26 -0
  34. package/dist/pages/scrapbook/types.d.ts.map +1 -0
  35. package/dist/pages/scrapbook/types.js +12 -0
  36. package/dist/pages/scrapbook/types.js.map +1 -0
  37. package/dist/pages/scrapbook.d.ts +8 -8
  38. package/dist/pages/scrapbook.d.ts.map +1 -1
  39. package/dist/pages/scrapbook.js +8 -600
  40. package/dist/pages/scrapbook.js.map +1 -1
  41. package/dist/pages/scrapbook.ts +11 -660
  42. package/dist/routes/api.d.ts.map +1 -1
  43. package/dist/routes/api.js +111 -2
  44. package/dist/routes/api.js.map +1 -1
  45. package/dist/routes/entry-annotation-body.d.ts +29 -0
  46. package/dist/routes/entry-annotation-body.d.ts.map +1 -1
  47. package/dist/routes/entry-annotation-body.js +80 -0
  48. package/dist/routes/entry-annotation-body.js.map +1 -1
  49. package/dist/routes/scrapbook-file.d.ts +1 -1
  50. package/dist/routes/scrapbook-file.js +1 -1
  51. package/dist/routes/scrapbook-mutation-dispatch.d.ts +29 -0
  52. package/dist/routes/scrapbook-mutation-dispatch.d.ts.map +1 -0
  53. package/dist/routes/scrapbook-mutation-dispatch.js +63 -0
  54. package/dist/routes/scrapbook-mutation-dispatch.js.map +1 -0
  55. package/dist/routes/scrapbook-mutation-envelope.d.ts +93 -0
  56. package/dist/routes/scrapbook-mutation-envelope.d.ts.map +1 -0
  57. package/dist/routes/scrapbook-mutation-envelope.js +147 -0
  58. package/dist/routes/scrapbook-mutation-envelope.js.map +1 -0
  59. package/dist/routes/scrapbook-mutations.d.ts +25 -7
  60. package/dist/routes/scrapbook-mutations.d.ts.map +1 -1
  61. package/dist/routes/scrapbook-mutations.js +67 -92
  62. package/dist/routes/scrapbook-mutations.js.map +1 -1
  63. package/dist/server.d.ts.map +1 -1
  64. package/dist/server.js +16 -2
  65. package/dist/server.js.map +1 -1
  66. package/package.json +2 -2
  67. package/dist/pages/review.d.ts +0 -68
  68. package/dist/pages/review.d.ts.map +0 -1
  69. package/dist/pages/review.js +0 -561
  70. package/dist/pages/review.js.map +0 -1
  71. package/dist/pages/review.ts +0 -710
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Scrapbook viewer — `/dev/scrapbook/:site/<path>`.
3
+ *
4
+ * Issue #161 redesign: aside-left folder card with numbered item list,
5
+ * vertical card grid with per-kind colored ribbons + always-visible foot
6
+ * toolbar + per-kind preview rendering, drop zone, secret section,
7
+ * single-expanded card invariant, aside cross-linking.
8
+ *
9
+ * The implementation is split across this directory (one module per
10
+ * concern, each under the 500-line cap). This file is the orchestrator:
11
+ * dispatch listing, build the render context, compose the chrome.
12
+ *
13
+ * Mockup: docs/superpowers/frontend-design/2026-05-02-review-redesign/scrapbook-redesign.html
14
+ * Spec: docs/superpowers/specs/2026-05-02-scrapbook-redesign-impl-spec.md
15
+ */
16
+ import type { StudioContext } from '../../routes/api.ts';
17
+ export { ScrapbookPageError } from './dispatch.ts';
18
+ export declare function renderScrapbookPage(ctx: StudioContext, site: string, path: string, opts?: {
19
+ entryId?: string;
20
+ }): Promise<string>;
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/pages/scrapbook/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAkBzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAEnD,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,aAAa,EAClB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GAC9B,OAAO,CAAC,MAAM,CAAC,CA+EjB"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Scrapbook viewer — `/dev/scrapbook/:site/<path>`.
3
+ *
4
+ * Issue #161 redesign: aside-left folder card with numbered item list,
5
+ * vertical card grid with per-kind colored ribbons + always-visible foot
6
+ * toolbar + per-kind preview rendering, drop zone, secret section,
7
+ * single-expanded card invariant, aside cross-linking.
8
+ *
9
+ * The implementation is split across this directory (one module per
10
+ * concern, each under the 500-line cap). This file is the orchestrator:
11
+ * dispatch listing, build the render context, compose the chrome.
12
+ *
13
+ * Mockup: docs/superpowers/frontend-design/2026-05-02-review-redesign/scrapbook-redesign.html
14
+ * Spec: docs/superpowers/specs/2026-05-02-scrapbook-redesign-impl-spec.md
15
+ */
16
+ import { html, unsafe } from "../html.js";
17
+ import { layout } from "../layout.js";
18
+ import { renderEditorialFolio } from "../chrome.js";
19
+ import { ScrapbookPageError, resolveListing } from "./dispatch.js";
20
+ import { countByKind, renderAside, renderBreadcrumb, renderCard, renderComposer, renderDropZone, renderFilterChips, renderSearch, renderSecretSection, } from "./render.js";
21
+ export { ScrapbookPageError } from "./dispatch.js";
22
+ export async function renderScrapbookPage(ctx, site, path, opts = {}) {
23
+ // Validate site against the project's configured site list. Without
24
+ // this check, an unknown site key reaches the path resolver and
25
+ // produces either an opaque error or a path traversal vector.
26
+ if (!(site in ctx.config.sites)) {
27
+ throw new ScrapbookPageError(`unknown site: ${site}`, 404);
28
+ }
29
+ const requestedEntryId = opts.entryId !== undefined && opts.entryId.length > 0 ? opts.entryId : null;
30
+ const { scrapbookDir, result, resolvedEntryId } = await resolveListing(ctx, site, path, requestedEntryId);
31
+ const items = result.items;
32
+ const secretItems = result.secretItems;
33
+ const totalSize = items.reduce((s, i) => s + i.size, 0);
34
+ const lastModified = items.reduce((acc, i) => {
35
+ if (!i.mtime)
36
+ return acc;
37
+ if (!acc || i.mtime > acc)
38
+ return i.mtime;
39
+ return acc;
40
+ }, null);
41
+ const counts = countByKind(items);
42
+ const folderLabel = path.split('/').filter(Boolean).pop() ?? path;
43
+ // Effective entryId for URL emission: prefer the request-supplied id
44
+ // (so the client's mutation requests round-trip through the same
45
+ // addressing mode) and fall back to the slug-mode lookup result.
46
+ const effectiveEntryId = requestedEntryId ?? resolvedEntryId;
47
+ const rctx = effectiveEntryId !== null
48
+ ? { studio: ctx, site, path, entryId: effectiveEntryId, scrapbookDir }
49
+ : { studio: ctx, site, path, scrapbookDir };
50
+ const cards = items.map((item, i) => renderCard(rctx, item, i));
51
+ const cardsHtml = cards.map((c) => c.__raw).join('');
52
+ const reviewLink = effectiveEntryId !== null
53
+ ? `/dev/editorial-review/entry/${effectiveEntryId}`
54
+ : null;
55
+ // The data-entry-id attribute is consumed by scrapbook-client.ts —
56
+ // when present, the client sends `entryId` on mutation requests so
57
+ // writes resolve via `scrapbookDirForEntry` (#191). Falls back to
58
+ // `data-path` slug-template addressing when absent.
59
+ // entryId comes from a UUID lookup — already validated against the
60
+ // calendar's CalendarEntry.id, so no escaping concern beyond the
61
+ // belt-and-braces unsafe wrapping for the conditional attribute.
62
+ const entryIdAttr = effectiveEntryId !== null
63
+ ? unsafe(` data-entry-id="${effectiveEntryId}"`)
64
+ : unsafe('');
65
+ const body = html `
66
+ ${renderEditorialFolio('content', `scrapbook · ${site}/${path}`)}
67
+ <main class="scrap-page" data-site="${site}" data-path="${path}"${entryIdAttr}>
68
+ ${renderAside(site, path, items, totalSize, lastModified, secretItems.length, reviewLink)}
69
+ <section class="scrap-main">
70
+ <header class="scrap-main-header">
71
+ ${renderBreadcrumb(site, path)}
72
+ ${renderSearch()}
73
+ </header>
74
+ ${renderFilterChips(counts)}
75
+ ${renderComposer()}
76
+ <ol class="scrap-cards" id="cards" data-scrap-cards>
77
+ ${unsafe(cardsHtml)}
78
+ </ol>
79
+ ${renderDropZone()}
80
+ ${renderSecretSection(rctx, secretItems)}
81
+ </section>
82
+ </main>`;
83
+ return layout({
84
+ title: `scrapbook · ${folderLabel} — dev`,
85
+ cssHrefs: [
86
+ '/static/css/editorial-review.css',
87
+ '/static/css/editorial-nav.css',
88
+ '/static/css/scrapbook.css',
89
+ '/static/css/blog-figure.css',
90
+ ],
91
+ bodyAttrs: 'data-review-ui="scrapbook"',
92
+ bodyHtml: body,
93
+ scriptModules: ['scrapbook-client'],
94
+ });
95
+ }
96
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/pages/scrapbook/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Scrapbook page render helpers — RawHtml emitters for cards, previews,
3
+ * the aside, the new-note composer, the drop zone, the secret section,
4
+ * and the breadcrumb / search / filter chrome.
5
+ *
6
+ * Reads filesystem only via `computeKindMeta` and `renderPreview`; both
7
+ * resolve through the pre-resolved `RenderCtx.scrapbookDir` (set by the
8
+ * dispatch step) so render stays free of path-resolution logic.
9
+ */
10
+ import { type ScrapbookItem } from '@deskwork/core/scrapbook';
11
+ import { type RawHtml } from '../html.ts';
12
+ import type { RenderCtx } from './types.ts';
13
+ export interface KindCounts {
14
+ all: number;
15
+ md: number;
16
+ img: number;
17
+ json: number;
18
+ js: number;
19
+ txt: number;
20
+ other: number;
21
+ }
22
+ export declare function countByKind(items: readonly ScrapbookItem[]): KindCounts;
23
+ /**
24
+ * Compute the per-kind extra meta string shown after the kind chip + size:
25
+ * md / txt → "{N} lines"
26
+ * json → "{N} keys" (root must be a plain object; otherwise empty)
27
+ * img → "{W} × {H}" (PNG only; other formats → empty)
28
+ * other → empty
29
+ *
30
+ * ENOENT (race-window with delete) returns empty so the card still
31
+ * renders; other errors propagate to the page renderer.
32
+ */
33
+ export declare function computeKindMeta(rctx: RenderCtx, item: ScrapbookItem, opts?: {
34
+ secret?: boolean;
35
+ }): string;
36
+ /**
37
+ * Server-side preview for the closed-state card. Img → bg-frame URL;
38
+ * md → italic Newsreader excerpt with frontmatter stripped; json → mono
39
+ * pre with parse-then-stringify pretty-print; txt → mono pre raw excerpt.
40
+ * Other / empty / binary-as-text → no preview block.
41
+ */
42
+ export declare function renderPreview(rctx: RenderCtx, item: ScrapbookItem, opts?: {
43
+ secret?: boolean;
44
+ }): RawHtml;
45
+ export declare function renderFilterChips(counts: KindCounts): RawHtml;
46
+ export declare function renderSearch(): RawHtml;
47
+ export declare function renderBreadcrumb(site: string, path: string): RawHtml;
48
+ export declare function renderAside(site: string, path: string, items: readonly ScrapbookItem[], totalSize: number, lastModified: string | null, secretCount: number, reviewLink: string | null): RawHtml;
49
+ export declare function renderCard(rctx: RenderCtx, item: ScrapbookItem, index: number, opts?: {
50
+ secret?: boolean;
51
+ }): RawHtml;
52
+ /**
53
+ * Inline new-note composer (Phase 34b — #166).
54
+ *
55
+ * Mirrors the pre-F1 inline composer (`44094ee^:scrapbook.ts:274-294`),
56
+ * adapted to the F1 `.scrap-*` design vocabulary. Hidden by default;
57
+ * the aside's `+ new note` button reveals it via the client wire-up.
58
+ *
59
+ * Per `.claude/rules/affordance-placement.md`: component-attached to
60
+ * the page (not a generic toolbar), placed where the resulting note
61
+ * will appear in sorted position. Direct manipulation: in-page form,
62
+ * filename + body + secret toggle visible inline, Cmd/Ctrl+S saves,
63
+ * Esc cancels. Replaces the F1 `window.prompt()` regression (#166).
64
+ */
65
+ export declare function renderComposer(): RawHtml;
66
+ export declare function renderDropZone(): RawHtml;
67
+ export declare function renderSecretSection(rctx: RenderCtx, secretItems: readonly ScrapbookItem[]): RawHtml;
68
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../src/pages/scrapbook/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAIL,KAAK,aAAa,EAEnB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAgB,KAAK,OAAO,EAAE,MAAM,YAAY,CAAC;AAQxD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAW5C,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,aAAa,EAAE,GAAG,UAAU,CAYvE;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,SAAS,EACf,IAAI,EAAE,aAAa,EACnB,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GAC9B,MAAM,CAwBR;AAsBD;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,SAAS,EACf,IAAI,EAAE,aAAa,EACnB,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GAC9B,OAAO,CAsCT;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAc7D;AAED,wBAAgB,YAAY,IAAI,OAAO,CAMtC;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAQpE;AAED,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,SAAS,aAAa,EAAE,EAC/B,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,GAAG,IAAI,GACxB,OAAO,CA2CT;AAED,wBAAgB,UAAU,CACxB,IAAI,EAAE,SAAS,EACf,IAAI,EAAE,aAAa,EACnB,KAAK,EAAE,MAAM,EACb,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GAC9B,OAAO,CAqDT;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAmBxC;AAED,wBAAgB,cAAc,IAAI,OAAO,CAMxC;AAED,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,SAAS,EACf,WAAW,EAAE,SAAS,aAAa,EAAE,GACpC,OAAO,CAcT"}
@@ -0,0 +1,315 @@
1
+ /**
2
+ * Scrapbook page render helpers — RawHtml emitters for cards, previews,
3
+ * the aside, the new-note composer, the drop zone, the secret section,
4
+ * and the breadcrumb / search / filter chrome.
5
+ *
6
+ * Reads filesystem only via `computeKindMeta` and `renderPreview`; both
7
+ * resolve through the pre-resolved `RenderCtx.scrapbookDir` (set by the
8
+ * dispatch step) so render stays free of path-resolution logic.
9
+ */
10
+ import { readFileSync } from 'node:fs';
11
+ import { formatRelativeTime, formatSize, scrapbookFilePathAtDir, } from '@deskwork/core/scrapbook';
12
+ import { html, unsafe } from "../html.js";
13
+ import { countJsonKeys, countLines, escapeHtml, previewExcerpt, } from "./text-helpers.js";
14
+ import { readImageDimensions } from "./image-readers.js";
15
+ const KIND_LABEL = {
16
+ md: 'MD',
17
+ img: 'IMG',
18
+ json: 'JSON',
19
+ js: 'JS',
20
+ txt: 'TXT',
21
+ other: '·',
22
+ };
23
+ export function countByKind(items) {
24
+ const counts = {
25
+ all: items.length,
26
+ md: 0,
27
+ img: 0,
28
+ json: 0,
29
+ js: 0,
30
+ txt: 0,
31
+ other: 0,
32
+ };
33
+ for (const i of items)
34
+ counts[i.kind]++;
35
+ return counts;
36
+ }
37
+ /**
38
+ * Compute the per-kind extra meta string shown after the kind chip + size:
39
+ * md / txt → "{N} lines"
40
+ * json → "{N} keys" (root must be a plain object; otherwise empty)
41
+ * img → "{W} × {H}" (PNG only; other formats → empty)
42
+ * other → empty
43
+ *
44
+ * ENOENT (race-window with delete) returns empty so the card still
45
+ * renders; other errors propagate to the page renderer.
46
+ */
47
+ export function computeKindMeta(rctx, item, opts = {}) {
48
+ if (item.kind !== 'md' && item.kind !== 'txt' && item.kind !== 'json' && item.kind !== 'img') {
49
+ return '';
50
+ }
51
+ let buf;
52
+ try {
53
+ const fullPath = scrapbookFilePathAtDir(rctx.scrapbookDir, item.name, opts.secret ? { secret: true } : {});
54
+ buf = readFileSync(fullPath);
55
+ }
56
+ catch (e) {
57
+ if (e instanceof Error && 'code' in e && e.code === 'ENOENT')
58
+ return '';
59
+ throw e;
60
+ }
61
+ if (item.kind === 'md' || item.kind === 'txt')
62
+ return `${countLines(buf)} lines`;
63
+ if (item.kind === 'json') {
64
+ const keys = countJsonKeys(buf);
65
+ return keys !== null ? `${keys} keys` : '';
66
+ }
67
+ // img
68
+ const dims = readImageDimensions(buf);
69
+ return dims ? `${dims.width} × ${dims.height}` : '';
70
+ }
71
+ /**
72
+ * Build the file-fetch URL for the read-only binary endpoint. Prefers
73
+ * entry-aware addressing when `rctx.entryId` is set (#205); falls back
74
+ * to slug-template addressing (`path=`) otherwise.
75
+ */
76
+ function buildFileFetchUrl(rctx, filename, secret) {
77
+ const params = new URLSearchParams({ site: rctx.site, name: filename });
78
+ if (rctx.entryId !== undefined && rctx.entryId.length > 0) {
79
+ params.set('entryId', rctx.entryId);
80
+ }
81
+ else {
82
+ params.set('path', rctx.path);
83
+ }
84
+ if (secret)
85
+ params.set('secret', '1');
86
+ return `/api/dev/scrapbook-file?${params.toString()}`;
87
+ }
88
+ /**
89
+ * Server-side preview for the closed-state card. Img → bg-frame URL;
90
+ * md → italic Newsreader excerpt with frontmatter stripped; json → mono
91
+ * pre with parse-then-stringify pretty-print; txt → mono pre raw excerpt.
92
+ * Other / empty / binary-as-text → no preview block.
93
+ */
94
+ export function renderPreview(rctx, item, opts = {}) {
95
+ const { secret = false } = opts;
96
+ if (item.kind === 'img') {
97
+ const url = buildFileFetchUrl(rctx, item.name, secret);
98
+ return unsafe(html `
99
+ <div class="scrap-preview scrap-preview--img" aria-hidden="true">
100
+ <div class="scrap-preview--img-frame" style="background-image: url(&quot;${url}&quot;);"></div>
101
+ </div>`);
102
+ }
103
+ if (item.kind !== 'md' && item.kind !== 'txt' && item.kind !== 'json') {
104
+ return unsafe('');
105
+ }
106
+ try {
107
+ const fullPath = scrapbookFilePathAtDir(rctx.scrapbookDir, item.name, secret ? { secret: true } : {});
108
+ const buf = readFileSync(fullPath);
109
+ const excerpt = previewExcerpt(buf, item.kind);
110
+ if (excerpt === null)
111
+ return unsafe('');
112
+ const safe = escapeHtml(excerpt);
113
+ if (item.kind === 'json' || item.kind === 'txt') {
114
+ return unsafe(html `
115
+ <pre class="scrap-preview scrap-preview--mono" aria-hidden="true">${unsafe(safe)}</pre>`);
116
+ }
117
+ return unsafe(html `
118
+ <div class="scrap-preview scrap-preview-md" aria-hidden="true"><p>${unsafe(safe)}</p></div>`);
119
+ }
120
+ catch (e) {
121
+ // ENOENT = file disappeared between listScrapbook and this read (race
122
+ // window with delete); rendering an empty preview is the right call.
123
+ // Anything else (EACCES, EISDIR, encoding bugs) propagates so the
124
+ // operator sees a real error instead of a silently-broken page.
125
+ if (e instanceof Error && 'code' in e && e.code === 'ENOENT') {
126
+ return unsafe('');
127
+ }
128
+ throw e;
129
+ }
130
+ }
131
+ export function renderFilterChips(counts) {
132
+ const chip = (kind, label, isAll = false) => unsafe(html `
133
+ <button class="scrap-filter" type="button" data-filter="${kind}"
134
+ aria-pressed="${isAll ? 'true' : 'false'}">${label} · ${counts[kind]}</button>`);
135
+ return unsafe(html `
136
+ <div class="scrap-filters" role="toolbar" aria-label="filter by kind">
137
+ ${chip('all', 'all', true)}
138
+ ${chip('md', 'md')}
139
+ ${chip('img', 'img')}
140
+ ${chip('json', 'json')}
141
+ ${chip('txt', 'txt')}
142
+ ${chip('other', 'other')}
143
+ </div>`);
144
+ }
145
+ export function renderSearch() {
146
+ return unsafe(html `
147
+ <div class="scrap-search">
148
+ <input type="search" placeholder="filter by name or content" aria-label="filter scrapbook" data-scrap-search />
149
+ <span class="scrap-search-kbd">/</span>
150
+ </div>`);
151
+ }
152
+ export function renderBreadcrumb(site, path) {
153
+ const segments = path.split('/').filter(Boolean);
154
+ const last = segments[segments.length - 1] ?? path;
155
+ return unsafe(html `
156
+ <nav class="scrap-breadcrumb" aria-label="hierarchy">
157
+ <a href="/dev/content/${site}">${site}</a><span class="sep">›</span>
158
+ <b>${last}</b>
159
+ </nav>`);
160
+ }
161
+ export function renderAside(site, path, items, totalSize, lastModified, secretCount, reviewLink) {
162
+ const lastModifiedLabel = lastModified ? formatRelativeTime(lastModified) : '—';
163
+ const publicCount = items.length;
164
+ const sizeLabel = formatSize(totalSize);
165
+ const folderLabel = path.split('/').filter(Boolean).pop() ?? path;
166
+ const fullPath = `${site}/${path}/scrapbook/`;
167
+ // #168 Phase 34 ship-pass — when this scrapbook belongs to a tracked
168
+ // calendar entry, expose a "← back to review" link so the operator
169
+ // who arrived from the entry-review surface (or via the dashboard's
170
+ // scrapbook chip) has an obvious path back. Pre-fix the only nav
171
+ // affordance was the breadcrumb's site link, which lands on the
172
+ // content tree, not the entry-review.
173
+ const backLink = reviewLink !== null
174
+ ? unsafe(html `<p class="scrap-aside-back"><a href="${reviewLink}">← back to review</a></p><hr />`)
175
+ : unsafe('');
176
+ return unsafe(html `
177
+ <aside class="scrap-aside">
178
+ ${backLink}
179
+ <p class="scrap-aside-kicker"><em>§</em> The folder</p>
180
+ <h1 class="scrap-aside-title">${folderLabel}</h1>
181
+ <p class="scrap-aside-meta">${site}</p>
182
+ <hr />
183
+ <p class="scrap-aside-totals">
184
+ <strong>${publicCount}</strong> public ·
185
+ <strong>${secretCount}</strong> secret ·
186
+ <em>${sizeLabel}</em>
187
+ </p>
188
+ <p class="scrap-aside-meta">last modified ${lastModifiedLabel}</p>
189
+ <hr />
190
+ <ol class="scrap-aside-list" data-scrap-aside-list>
191
+ ${items.map((item, i) => {
192
+ const seq = String(i + 1).padStart(2, '0');
193
+ return unsafe(html `<li><span class="num">${seq}</span><a href="#item-${i + 1}" data-scrap-aside-link>${item.name}</a></li>`);
194
+ })}
195
+ </ol>
196
+ <hr />
197
+ <div class="scrap-aside-actions">
198
+ <button class="scrap-aside-btn scrap-aside-btn--primary" type="button" data-action="new-note">+ new note</button>
199
+ <button class="scrap-aside-btn" type="button" data-action="upload">+ upload file</button>
200
+ </div>
201
+ <hr />
202
+ <p class="scrap-aside-path">${fullPath}</p>
203
+ </aside>`);
204
+ }
205
+ export function renderCard(rctx, item, index, opts = {}) {
206
+ const { secret = false } = opts;
207
+ const seq = String(index + 1).padStart(2, '0');
208
+ const kindLabel = KIND_LABEL[item.kind];
209
+ const kindClass = item.kind === 'other' ? '' : `scrap-kind--${item.kind}`;
210
+ const time = item.mtime
211
+ ? html `<time class="scrap-time" datetime="${item.mtime}">${formatRelativeTime(item.mtime)}</time>`
212
+ : '';
213
+ const preview = renderPreview(rctx, item, { secret });
214
+ const kindMeta = computeKindMeta(rctx, item, { secret });
215
+ const kindMetaHtml = kindMeta
216
+ ? unsafe(html `<span>·</span><span>${kindMeta}</span>`)
217
+ : unsafe('');
218
+ const editBtn = item.kind === 'img'
219
+ ? unsafe('')
220
+ : unsafe(html `<button class="scrap-tool" type="button" data-action="edit">edit</button>`);
221
+ // Secret cards get id="secret-item-N" to disambiguate from public ids in
222
+ // restoreFromHash + aside cross-link lookups (F4 contract); the
223
+ // mark-secret action toggle reads "mark public" since clicking it moves
224
+ // the card OUT of the secret section.
225
+ const id = secret ? `secret-item-${index + 1}` : `item-${index + 1}`;
226
+ const markSecretLabel = secret ? 'mark public' : 'mark secret';
227
+ const dataSecretAttr = secret ? ' data-secret="true"' : '';
228
+ // #164 Phase 34b — small ⚿ glyph next to .scrap-name on secret
229
+ // cards. Provides visual continuity for the secret marker once a
230
+ // card is expanded (where it grows outside the grouped section's
231
+ // visual scope).
232
+ const secretGlyph = secret
233
+ ? unsafe(html `<span class="scrap-name-secret-mark" aria-label="secret" title="secret — never published">⚿</span>`)
234
+ : unsafe('');
235
+ return unsafe(html `
236
+ <li class="scrap-card" data-kind="${item.kind}" data-state="closed" id="${id}"${unsafe(dataSecretAttr)}>
237
+ <div class="scrap-card-head">
238
+ <span class="scrap-seq">N° ${seq}</span>
239
+ ${secretGlyph}
240
+ <span class="scrap-name" data-action="open">${item.name}</span>
241
+ ${unsafe(time)}
242
+ </div>
243
+ <div class="scrap-card-meta">
244
+ <span class="scrap-kind ${kindClass}">${kindLabel}</span>
245
+ <span class="scrap-size">${formatSize(item.size)}</span>
246
+ ${kindMetaHtml}
247
+ </div>
248
+ ${preview}
249
+ <div class="scrap-card-foot">
250
+ <button class="scrap-tool scrap-tool--primary" type="button" data-action="open">open</button>
251
+ ${editBtn}
252
+ <button class="scrap-tool" type="button" data-action="rename">rename</button>
253
+ <button class="scrap-tool" type="button" data-action="mark-secret">${markSecretLabel}</button>
254
+ <span class="spacer"></span>
255
+ <button class="scrap-tool scrap-tool--delete" type="button" data-action="delete">delete</button>
256
+ </div>
257
+ </li>`);
258
+ }
259
+ /**
260
+ * Inline new-note composer (Phase 34b — #166).
261
+ *
262
+ * Mirrors the pre-F1 inline composer (`44094ee^:scrapbook.ts:274-294`),
263
+ * adapted to the F1 `.scrap-*` design vocabulary. Hidden by default;
264
+ * the aside's `+ new note` button reveals it via the client wire-up.
265
+ *
266
+ * Per `.claude/rules/affordance-placement.md`: component-attached to
267
+ * the page (not a generic toolbar), placed where the resulting note
268
+ * will appear in sorted position. Direct manipulation: in-page form,
269
+ * filename + body + secret toggle visible inline, Cmd/Ctrl+S saves,
270
+ * Esc cancels. Replaces the F1 `window.prompt()` regression (#166).
271
+ */
272
+ export function renderComposer() {
273
+ return unsafe(html `
274
+ <form class="scrap-composer" data-scrap-composer hidden>
275
+ <header class="scrap-composer-head">
276
+ <span class="scrap-composer-glyph" aria-hidden="true">✎</span>
277
+ <span class="scrap-composer-kicker">NEW NOTE</span>
278
+ <input type="text" class="scrap-composer-filename" data-composer-filename
279
+ placeholder="note-name.md" aria-label="new note filename" />
280
+ <label class="scrap-composer-secret" title="save under scrapbook/secret/ — never published">
281
+ <input type="checkbox" data-composer-secret />
282
+ <span>secret</span>
283
+ </label>
284
+ <button class="scrap-tool" type="button" data-action="composer-cancel">cancel</button>
285
+ <button class="scrap-tool scrap-tool--primary" type="submit" data-action="composer-save">save →</button>
286
+ </header>
287
+ <textarea class="scrap-composer-body" data-composer-body
288
+ placeholder="Write the note in markdown. Cmd/Ctrl+S saves, Esc cancels."
289
+ aria-label="new note body" rows="8"></textarea>
290
+ </form>`);
291
+ }
292
+ export function renderDropZone() {
293
+ return unsafe(html `
294
+ <div class="scrap-drop" role="button" tabindex="0" data-action="upload"
295
+ aria-label="Drop a file here, or press Enter to pick one">
296
+ ── drop a file here, or pick one ──
297
+ </div>`);
298
+ }
299
+ export function renderSecretSection(rctx, secretItems) {
300
+ if (secretItems.length === 0)
301
+ return unsafe('');
302
+ const cards = secretItems.map((item, i) => renderCard(rctx, item, i, { secret: true }));
303
+ return unsafe(html `
304
+ <section class="scrap-secret" aria-label="secret items">
305
+ <header class="scrap-secret-head">
306
+ <span class="scrap-secret-mark" aria-hidden="true">⚿</span>
307
+ <h2 class="scrap-secret-title">Secret</h2>
308
+ <span class="scrap-secret-badge">private — never published</span>
309
+ </header>
310
+ <ol class="scrap-cards">
311
+ ${cards}
312
+ </ol>
313
+ </section>`);
314
+ }
315
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../../../src/pages/scrapbook/render.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Scrapbook text-content helpers — pure Buffer/string utilities for
3
+ * rendering closed-state previews and per-kind meta strings.
4
+ */
5
+ export declare function escapeHtml(s: string): string;
6
+ /**
7
+ * Strip a YAML frontmatter block from the top of an md file. Only strips
8
+ * the leading `---\n...\n---\n` block; body-level `---` separators (Setext
9
+ * H2 underline, thematic break) are preserved because the function only
10
+ * looks at the first 4 chars for the opener.
11
+ */
12
+ export declare function stripFrontmatter(text: string): string;
13
+ /**
14
+ * Build the closed-state preview excerpt for md/json/txt. Returns null
15
+ * when there's nothing useful to render — empty file, frontmatter-only
16
+ * file, or binary masquerading as text — so the caller can omit the
17
+ * preview block entirely (matches "other" kind treatment, avoids the
18
+ * 6rem min-height void).
19
+ *
20
+ * For json: pretty-print via JSON.parse + JSON.stringify(_, null, 2) so
21
+ * minified single-line files still render multi-line. Falls back to raw
22
+ * content on parse error (bad JSON is still readable as text).
23
+ *
24
+ * Binary detection: NUL byte presence after UTF-8 decode. Real text
25
+ * almost never has NUL; binary files have it within the first KB.
26
+ */
27
+ export declare function previewExcerpt(buf: Buffer, kind: 'md' | 'json' | 'txt'): string | null;
28
+ /**
29
+ * Count lines in a text file: number of `\n` bytes plus 1 if the last
30
+ * byte isn't `\n` (so a 3-line file whether or not it has a trailing
31
+ * newline reports 3).
32
+ */
33
+ export declare function countLines(buf: Buffer): number;
34
+ /**
35
+ * Count top-level keys in a JSON object. Returns null if the file is not
36
+ * valid JSON or its root is not a plain object (arrays, primitives →
37
+ * null; caller renders no extra meta).
38
+ */
39
+ export declare function countJsonKeys(buf: Buffer): number | null;
40
+ //# sourceMappingURL=text-helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text-helpers.d.ts","sourceRoot":"","sources":["../../../src/pages/scrapbook/text-helpers.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAO5C;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKrD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,IAAI,CAetF;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAK9C;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAUxD"}