plotlink-ows 1.0.32 → 1.2.94

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 (145) hide show
  1. package/README.md +4 -0
  2. package/app/lib/agent-command.ts +85 -0
  3. package/app/lib/agent-readiness.ts +133 -0
  4. package/app/lib/apply-schema.ts +55 -0
  5. package/app/lib/bubble-text.ts +160 -0
  6. package/app/lib/cartoon-coach.ts +198 -0
  7. package/app/lib/cartoon-markdown.ts +83 -0
  8. package/app/lib/cartoon-prompt.ts +122 -0
  9. package/app/lib/cartoon-readiness.ts +811 -0
  10. package/app/lib/clean-image-sync.ts +245 -0
  11. package/app/lib/codex-images.ts +152 -0
  12. package/app/lib/cut-asset-diagnostics.ts +120 -0
  13. package/app/lib/cuts.ts +302 -0
  14. package/app/lib/fonts.ts +109 -0
  15. package/app/lib/generate-claude-md.ts +10 -3
  16. package/app/lib/generate-story-instructions.ts +731 -0
  17. package/app/lib/image-asset-validate.ts +123 -0
  18. package/app/lib/lettering-status.ts +133 -0
  19. package/app/lib/overlays.ts +637 -0
  20. package/app/lib/paths.ts +10 -0
  21. package/app/lib/public-title.ts +65 -0
  22. package/app/lib/publish.ts +16 -2
  23. package/app/lib/story-progress.ts +243 -0
  24. package/app/lib/terminal-protocol.ts +16 -0
  25. package/app/lib/terminal-redact.ts +50 -0
  26. package/app/prisma/schema.sql +25 -0
  27. package/app/routes/agent.ts +42 -0
  28. package/app/routes/codex-images.ts +67 -0
  29. package/app/routes/publish.ts +209 -28
  30. package/app/routes/stories.ts +961 -5
  31. package/app/routes/terminal.ts +383 -31
  32. package/app/server.ts +47 -12
  33. package/app/vite.config.ts +6 -0
  34. package/app/web/components/CartoonPreview.tsx +267 -0
  35. package/app/web/components/CartoonPublishPage.tsx +407 -0
  36. package/app/web/components/CartoonPublishPreview.tsx +121 -0
  37. package/app/web/components/CartoonStepGuide.tsx +90 -0
  38. package/app/web/components/CartoonWorkflowNav.tsx +68 -0
  39. package/app/web/components/CodexImportPicker.tsx +230 -0
  40. package/app/web/components/CutListPanel.tsx +1299 -0
  41. package/app/web/components/EpisodesPage.tsx +80 -0
  42. package/app/web/components/FinishEpisodePanel.tsx +151 -0
  43. package/app/web/components/Layout.tsx +7 -4
  44. package/app/web/components/LetteringEditor.tsx +1141 -0
  45. package/app/web/components/PreviewPanel.tsx +1017 -144
  46. package/app/web/components/Settings.tsx +63 -0
  47. package/app/web/components/StoriesPage.tsx +710 -33
  48. package/app/web/components/StoryBrowser.tsx +22 -14
  49. package/app/web/components/StoryInfoPage.tsx +266 -0
  50. package/app/web/components/StoryProgressPanel.tsx +516 -0
  51. package/app/web/components/TerminalPanel.tsx +233 -11
  52. package/app/web/components/WorkflowCoach.tsx +128 -0
  53. package/app/web/components/asset-image.tsx +114 -0
  54. package/app/web/components/asset-test-utils.ts +44 -0
  55. package/app/web/components/export-cut.ts +320 -0
  56. package/app/web/dist/assets/export-cut-nKQ_n2-J.js +1 -0
  57. package/app/web/dist/assets/index-BAZGwVwj.js +143 -0
  58. package/app/web/dist/assets/index-DoXH2OlP.css +32 -0
  59. package/app/web/dist/index.html +2 -2
  60. package/app/web/lib/cartoon-publish-summary.ts +43 -0
  61. package/app/web/lib/codex-import.ts +94 -0
  62. package/app/web/lib/image-compress.ts +53 -0
  63. package/app/web/lib/import-image.ts +58 -0
  64. package/app/web/lib/publish-helpers.ts +385 -0
  65. package/app/web/lib/upload-retry.ts +130 -0
  66. package/app/web/lib/verify-public-title.ts +105 -0
  67. package/app/web/styles.css +9 -0
  68. package/bin/plotlink-ows.js +53 -16
  69. package/bin/startup-plan.cjs +58 -0
  70. package/lib/genres.ts +92 -0
  71. package/package.json +60 -20
  72. package/scripts/gen-schema-sql.mjs +49 -0
  73. package/scripts/package-hygiene.mjs +116 -0
  74. package/scripts/preflight.mjs +173 -0
  75. package/scripts/start-smoke.mjs +128 -0
  76. package/app/node_modules/.prisma/local-client/client.d.ts +0 -1
  77. package/app/node_modules/.prisma/local-client/client.js +0 -5
  78. package/app/node_modules/.prisma/local-client/default.d.ts +0 -1
  79. package/app/node_modules/.prisma/local-client/default.js +0 -5
  80. package/app/node_modules/.prisma/local-client/edge.d.ts +0 -1
  81. package/app/node_modules/.prisma/local-client/edge.js +0 -184
  82. package/app/node_modules/.prisma/local-client/index-browser.js +0 -173
  83. package/app/node_modules/.prisma/local-client/index.d.ts +0 -3304
  84. package/app/node_modules/.prisma/local-client/index.js +0 -207
  85. package/app/node_modules/.prisma/local-client/libquery_engine-darwin-arm64.dylib.node +0 -0
  86. package/app/node_modules/.prisma/local-client/package.json +0 -183
  87. package/app/node_modules/.prisma/local-client/query_engine_bg.js +0 -2
  88. package/app/node_modules/.prisma/local-client/query_engine_bg.wasm +0 -0
  89. package/app/node_modules/.prisma/local-client/runtime/edge-esm.js +0 -35
  90. package/app/node_modules/.prisma/local-client/runtime/edge.js +0 -35
  91. package/app/node_modules/.prisma/local-client/runtime/index-browser.d.ts +0 -370
  92. package/app/node_modules/.prisma/local-client/runtime/index-browser.js +0 -17
  93. package/app/node_modules/.prisma/local-client/runtime/library.d.ts +0 -3982
  94. package/app/node_modules/.prisma/local-client/runtime/library.js +0 -147
  95. package/app/node_modules/.prisma/local-client/runtime/react-native.js +0 -84
  96. package/app/node_modules/.prisma/local-client/runtime/wasm-compiler-edge.js +0 -85
  97. package/app/node_modules/.prisma/local-client/runtime/wasm-engine-edge.js +0 -38
  98. package/app/node_modules/.prisma/local-client/schema.prisma +0 -21
  99. package/app/node_modules/.prisma/local-client/wasm-edge-light-loader.mjs +0 -5
  100. package/app/node_modules/.prisma/local-client/wasm-worker-loader.mjs +0 -5
  101. package/app/node_modules/.prisma/local-client/wasm.d.ts +0 -1
  102. package/app/node_modules/.prisma/local-client/wasm.js +0 -191
  103. package/app/web/dist/assets/index-B-2Ft7Yv.css +0 -32
  104. package/app/web/dist/assets/index-BFw-v-OZ.js +0 -134
  105. package/packages/cli/node_modules/commander/LICENSE +0 -22
  106. package/packages/cli/node_modules/commander/Readme.md +0 -1149
  107. package/packages/cli/node_modules/commander/esm.mjs +0 -16
  108. package/packages/cli/node_modules/commander/index.js +0 -24
  109. package/packages/cli/node_modules/commander/lib/argument.js +0 -149
  110. package/packages/cli/node_modules/commander/lib/command.js +0 -2662
  111. package/packages/cli/node_modules/commander/lib/error.js +0 -39
  112. package/packages/cli/node_modules/commander/lib/help.js +0 -709
  113. package/packages/cli/node_modules/commander/lib/option.js +0 -367
  114. package/packages/cli/node_modules/commander/lib/suggestSimilar.js +0 -101
  115. package/packages/cli/node_modules/commander/package-support.json +0 -16
  116. package/packages/cli/node_modules/commander/package.json +0 -82
  117. package/packages/cli/node_modules/commander/typings/esm.d.mts +0 -3
  118. package/packages/cli/node_modules/commander/typings/index.d.ts +0 -1045
  119. package/packages/cli/node_modules/resolve-from/index.d.ts +0 -31
  120. package/packages/cli/node_modules/resolve-from/index.js +0 -47
  121. package/packages/cli/node_modules/resolve-from/license +0 -9
  122. package/packages/cli/node_modules/resolve-from/package.json +0 -36
  123. package/packages/cli/node_modules/resolve-from/readme.md +0 -72
  124. package/packages/cli/node_modules/tsup/LICENSE +0 -21
  125. package/packages/cli/node_modules/tsup/README.md +0 -75
  126. package/packages/cli/node_modules/tsup/assets/cjs_shims.js +0 -13
  127. package/packages/cli/node_modules/tsup/assets/esm_shims.js +0 -9
  128. package/packages/cli/node_modules/tsup/assets/package.json +0 -3
  129. package/packages/cli/node_modules/tsup/dist/chunk-DI5BO6XE.js +0 -153
  130. package/packages/cli/node_modules/tsup/dist/chunk-JZ25TPTY.js +0 -42
  131. package/packages/cli/node_modules/tsup/dist/chunk-PEEXUWMS.js +0 -6
  132. package/packages/cli/node_modules/tsup/dist/chunk-TWFEYLU4.js +0 -352
  133. package/packages/cli/node_modules/tsup/dist/chunk-VGC3FXLU.js +0 -203
  134. package/packages/cli/node_modules/tsup/dist/cli-default.js +0 -12
  135. package/packages/cli/node_modules/tsup/dist/cli-main.js +0 -8
  136. package/packages/cli/node_modules/tsup/dist/cli-node.js +0 -14
  137. package/packages/cli/node_modules/tsup/dist/index.d.ts +0 -511
  138. package/packages/cli/node_modules/tsup/dist/index.js +0 -1711
  139. package/packages/cli/node_modules/tsup/dist/rollup.js +0 -6949
  140. package/packages/cli/node_modules/tsup/package.json +0 -99
  141. package/packages/cli/node_modules/tsup/schema.json +0 -362
  142. package/public/screenshot-1.png +0 -0
  143. package/public/screenshot-2.png +0 -0
  144. package/public/screenshot-3.png +0 -0
  145. package/scripts/e2e-verify.ts +0 -1100
@@ -0,0 +1,80 @@
1
+ import { useEffect, useState } from "react";
2
+ import type { StoryProgress, EpisodeProgress } from "@app-lib/story-progress";
3
+
4
+ interface EpisodesPageProps {
5
+ storyName: string;
6
+ authFetch: (url: string, opts?: RequestInit) => Promise<Response>;
7
+ onOpenFile: (storyName: string, file: string) => void;
8
+ }
9
+
10
+ /**
11
+ * "Episodes" workflow page for cartoon stories (#439, spec §2 nav target).
12
+ *
13
+ * A reader-ordered list of the story's episodes (Genesis = Episode 1, plot-NN =
14
+ * Episode N+1) with status, so a normal creator manages episodes without the
15
+ * file tree. Clicking one opens it in the preview. The dedicated rich episode
16
+ * manager is a later ticket (§11); this is the workflow nav's episodes target.
17
+ */
18
+ export function EpisodesPage({ storyName, authFetch, onOpenFile }: EpisodesPageProps) {
19
+ const [episodes, setEpisodes] = useState<EpisodeProgress[] | null>(null);
20
+ const [loading, setLoading] = useState(true);
21
+
22
+ useEffect(() => {
23
+ let cancelled = false;
24
+ const load = async () => {
25
+ setLoading(true);
26
+ try {
27
+ const res = await authFetch(`/api/stories/${storyName}/progress`);
28
+ const data: StoryProgress | null = res.ok ? await res.json() : null;
29
+ if (!cancelled) {
30
+ setEpisodes(Array.isArray(data?.episodes) ? data!.episodes : null);
31
+ setLoading(false);
32
+ }
33
+ } catch {
34
+ if (!cancelled) { setEpisodes(null); setLoading(false); }
35
+ }
36
+ };
37
+ load();
38
+ return () => { cancelled = true; };
39
+ }, [storyName, authFetch]);
40
+
41
+ if (loading) {
42
+ return <div className="h-full flex items-center justify-center text-muted text-sm" data-testid="episodes-loading">Loading episodes…</div>;
43
+ }
44
+ if (!episodes) {
45
+ return <div className="h-full flex items-center justify-center text-muted text-sm">Could not load episodes.</div>;
46
+ }
47
+
48
+ return (
49
+ <div className="h-full overflow-y-auto px-4 py-4" data-testid="episodes-page">
50
+ <h2 className="text-base font-serif text-foreground">Episodes</h2>
51
+ <p className="mt-0.5 text-[11px] text-muted">Genesis is Episode 1; each plot file is the next episode.</p>
52
+
53
+ {episodes.length === 0 ? (
54
+ <p className="mt-4 text-xs text-muted italic" data-testid="episodes-empty">No episodes yet — write the Genesis to start Episode 1.</p>
55
+ ) : (
56
+ <ol className="mt-3 flex flex-col gap-1">
57
+ {episodes.map((ep) => (
58
+ <li key={ep.file}>
59
+ <button
60
+ onClick={() => onOpenFile(storyName, ep.file)}
61
+ data-testid={`episodes-row-${ep.file}`}
62
+ data-state={ep.state}
63
+ className="w-full text-left flex items-start gap-2 rounded px-2 py-1.5 hover:bg-surface"
64
+ >
65
+ <span className="min-w-0 flex-1">
66
+ <span className="flex items-center gap-1.5">
67
+ <span className="text-xs font-medium text-foreground">{ep.label}</span>
68
+ {ep.title && <span className="text-[11px] text-muted truncate">· {ep.title}</span>}
69
+ <span className="ml-auto text-[10px] text-muted">{ep.file}</span>
70
+ </span>
71
+ <span className="block text-[11px] text-muted">{ep.summary}</span>
72
+ </span>
73
+ </button>
74
+ </li>
75
+ ))}
76
+ </ol>
77
+ )}
78
+ </div>
79
+ );
80
+ }
@@ -0,0 +1,151 @@
1
+ import { groupCartoonIssues, type CartoonChecklist } from "@app-lib/cartoon-readiness";
2
+
3
+ type StepStatus = "done" | "current" | "todo";
4
+
5
+ const STATUS_MARK: Record<StepStatus, string> = { done: "✓", current: "▸", todo: "○" };
6
+
7
+ interface FinishEpisodePanelProps {
8
+ /** Writer-language production checklist for this episode (null ⇒ not a cartoon plot). */
9
+ checklist: CartoonChecklist | null;
10
+ /** Flat readiness/upload issues; grouped by actionable step for display. */
11
+ issues: string[];
12
+ /** Run the guided finish flow (upload finals → prepare episode markdown, in order). */
13
+ onFinish: () => void;
14
+ /** True while the finish flow is running. */
15
+ finishing: boolean;
16
+ /** Live progress line shown on the button while finishing (e.g. "Uploading cut 2 (2/7)…"). */
17
+ progressText?: string;
18
+ /** Whether there is anything left to finish (≥1 exported final not yet published). */
19
+ canFinish: boolean;
20
+ /** The publish markdown is built and passes readiness — "Episode sequence prepared". */
21
+ markdownReady?: boolean;
22
+ /** The episode is published on-chain. */
23
+ published?: boolean;
24
+ }
25
+
26
+ interface DisplayStep {
27
+ key: string;
28
+ label: string;
29
+ status: StepStatus;
30
+ detail: string | null;
31
+ }
32
+
33
+ /**
34
+ * Guided "Finish episode" flow for a cartoon plot (#414).
35
+ *
36
+ * The end-to-end pilot showed the production tail (export → upload → prepare
37
+ * markdown → publish) was technically complete but fragmented: a writer had to know
38
+ * which low-level button to click and read a flat wall of "Cut N: …" errors. This
39
+ * panel makes the tail one guided surface in writer language: it shows the six
40
+ * production steps with live status, offers ONE primary "Finish episode" action
41
+ * that runs the remaining automatable steps in order (resumable — already-uploaded
42
+ * cuts are skipped by the caller), and groups any blockers under the actionable
43
+ * step heading instead of a long red list. The lower-level controls stay available
44
+ * elsewhere in the workspace for manual recovery.
45
+ *
46
+ * Renders nothing when there is no checklist (e.g. a fiction plot or an unparsed
47
+ * cut plan), so it never appears outside the cartoon flow.
48
+ */
49
+ export function FinishEpisodePanel({
50
+ checklist,
51
+ issues,
52
+ onFinish,
53
+ finishing,
54
+ progressText,
55
+ canFinish,
56
+ markdownReady = false,
57
+ published = false,
58
+ }: FinishEpisodePanelProps) {
59
+ if (!checklist || checklist.steps.length === 0) return null;
60
+
61
+ const groups = groupCartoonIssues(issues);
62
+
63
+ // The base checklist (plan → upload) models per-cut art/lettering/export/upload
64
+ // progress; it has no notion of the publish markdown being assembled. #414 needs
65
+ // the post-upload tail modelled explicitly, so replace its single "publish" step
66
+ // with two real states: "Episode sequence prepared" (markdown built + ready) and
67
+ // "Ready to publish" (which becomes "Published" once it's on-chain).
68
+ const uploadDone = checklist.steps.find((s) => s.key === "upload")?.status === "done";
69
+ const ready = uploadDone && markdownReady && !published; // ready to publish, not yet published
70
+
71
+ const assembleStatus: StepStatus = published || markdownReady ? "done" : uploadDone ? "current" : "todo";
72
+ const readyStatus: StepStatus = published ? "done" : ready ? "current" : "todo";
73
+
74
+ const steps: DisplayStep[] = [
75
+ ...checklist.steps.filter((s) => s.key !== "publish"),
76
+ { key: "assemble", label: "Episode sequence prepared", status: assembleStatus, detail: null },
77
+ { key: "ready", label: published ? "Published to PlotLink" : "Ready to publish", status: readyStatus, detail: null },
78
+ ];
79
+
80
+ const buttonLabel = finishing
81
+ ? progressText || "Finishing…"
82
+ : published
83
+ ? "Published ✓"
84
+ : ready
85
+ ? "Episode ready to publish"
86
+ : "Finish episode";
87
+
88
+ return (
89
+ <div
90
+ className="px-3 py-2 border-b border-border bg-surface/50 space-y-2 flex-shrink-0"
91
+ data-testid="finish-episode-panel"
92
+ >
93
+ <div className="flex items-center justify-between gap-2">
94
+ <span className="text-[11px] font-medium text-foreground">Finish episode</span>
95
+ {checklist.nextStep && (
96
+ <span className="text-[10px] text-muted truncate" data-testid="finish-next-step">
97
+ Next: {checklist.nextStep}
98
+ </span>
99
+ )}
100
+ </div>
101
+
102
+ {/* Writer-language step status — the exact webtoon production sequence. */}
103
+ <ol className="flex flex-wrap gap-1.5">
104
+ {steps.map((s) => (
105
+ <li
106
+ key={s.key}
107
+ data-testid={`finish-step-${s.key}`}
108
+ data-status={s.status}
109
+ className={`flex items-center gap-1 rounded border px-1.5 py-0.5 text-[10px] ${
110
+ s.status === "current"
111
+ ? "border-accent/40 bg-accent/10 text-accent"
112
+ : s.status === "done"
113
+ ? "border-border bg-background/70 text-foreground"
114
+ : "border-border/70 bg-background/40 text-muted"
115
+ }`}
116
+ >
117
+ <span aria-hidden>{STATUS_MARK[s.status]}</span>
118
+ <span>{s.label}</span>
119
+ {s.detail && <span className="text-muted">· {s.detail}</span>}
120
+ </li>
121
+ ))}
122
+ </ol>
123
+
124
+ <button
125
+ onClick={onFinish}
126
+ disabled={finishing || !canFinish}
127
+ data-testid="finish-episode-btn"
128
+ title="Upload the exported final panels, then prepare the episode for publishing — picks up where it left off"
129
+ className="px-3 py-1 text-xs border border-accent/40 text-accent rounded hover:bg-accent/5 disabled:opacity-50"
130
+ >
131
+ {buttonLabel}
132
+ </button>
133
+
134
+ {/* Blockers grouped by the step that fixes them, not a flat red list. */}
135
+ {groups.length > 0 && (
136
+ <div className="space-y-1.5" data-testid="finish-issues">
137
+ {groups.map((g) => (
138
+ <div key={g.key} data-testid={`finish-issue-group-${g.key}`} className="text-[10px]">
139
+ <p className="font-medium text-amber-700">{g.title}</p>
140
+ <ul className="ml-3 list-disc text-muted">
141
+ {g.lines.map((line, i) => (
142
+ <li key={i}>{line}</li>
143
+ ))}
144
+ </ul>
145
+ </div>
146
+ ))}
147
+ </div>
148
+ )}
149
+ </div>
150
+ );
151
+ }
@@ -154,7 +154,7 @@ export function Layout({ token, onLogout }: { token: string; onLogout: () => voi
154
154
  <div className="text-center space-y-2">
155
155
  <h1 className="text-2xl font-serif text-foreground">Write. Publish. Earn.</h1>
156
156
  <p className="text-muted text-sm">
157
- Claude CLI writes stories. You publish them on-chain.
157
+ Claude or Codex helps create your story. You publish it on-chain.
158
158
  </p>
159
159
  </div>
160
160
 
@@ -173,12 +173,15 @@ export function Layout({ token, onLogout }: { token: string; onLogout: () => voi
173
173
  <div className="rounded border border-border p-4 space-y-2 text-xs text-muted">
174
174
  <p className="font-medium text-foreground text-sm">How it works</p>
175
175
  <ol className="space-y-1.5 list-decimal list-inside">
176
- <li>Open the <strong>Stories</strong> tab — Claude CLI launches in the terminal</li>
177
- <li>Tell Claude your story idea — it brainstorms, outlines, and writes</li>
178
- <li>Review the live preview as Claude creates files</li>
176
+ <li>Open the <strong>Stories</strong> tab — your writing agent launches in the terminal</li>
177
+ <li>Tell the agent your story idea — it brainstorms, outlines, and writes</li>
178
+ <li>Review the live preview as the agent creates files</li>
179
179
  <li>Click <strong>Publish</strong> to put your story on-chain</li>
180
180
  <li>Earn 5% royalties on every trade at <a href="https://plotlink.xyz" target="_blank" rel="noopener noreferrer" className="text-accent underline">plotlink.xyz</a></li>
181
181
  </ol>
182
+ <p className="text-[11px] text-muted">
183
+ Fiction defaults to Claude; cartoon mode uses Codex for clean-image generation.
184
+ </p>
182
185
  </div>
183
186
 
184
187
  <div className="text-center">