@agent-native/core 0.109.1 → 0.109.2

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 (51) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +7 -0
  3. package/corpus/core/docs/content/integrations.mdx +87 -253
  4. package/corpus/core/package.json +1 -1
  5. package/corpus/core/src/client/blocks/library/diagram.config.ts +10 -1
  6. package/corpus/core/src/client/blocks/library/diagram.tsx +9 -4
  7. package/corpus/core/src/deploy/build.ts +63 -3
  8. package/corpus/templates/clips/.agents/skills/dictate/SKILL.md +28 -5
  9. package/corpus/templates/clips/.agents/skills/meetings/SKILL.md +7 -0
  10. package/corpus/templates/clips/.agents/skills/recording/SKILL.md +23 -0
  11. package/corpus/templates/clips/AGENTS.md +10 -0
  12. package/corpus/templates/clips/actions/create-dictation.ts +29 -4
  13. package/corpus/templates/clips/actions/finalize-recording.ts +2 -4
  14. package/corpus/templates/clips/actions/get-recording-player-data.ts +16 -6
  15. package/corpus/templates/clips/actions/set-mobile-capture-state.ts +37 -0
  16. package/corpus/templates/clips/actions/update-dictation.ts +9 -1
  17. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +13 -2
  18. package/corpus/templates/clips/app/routes/share.$shareId.tsx +13 -2
  19. package/corpus/templates/clips/changelog/2026-07-17-capture-dictation-background-meeting-audio-and-camera-videos.md +6 -0
  20. package/corpus/templates/clips/changelog/2026-07-17-delayed-upload-verification-keeps-local-backups-retryable.md +6 -0
  21. package/corpus/templates/clips/desktop/src/app.tsx +17 -0
  22. package/corpus/templates/clips/desktop/src/lib/processing-backup-recovery.ts +19 -0
  23. package/corpus/templates/clips/desktop/src/lib/recorder.ts +60 -35
  24. package/corpus/templates/clips/desktop/src/lib/upload-verification.ts +12 -0
  25. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +1 -0
  26. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +14 -0
  27. package/corpus/templates/clips/server/db/schema.ts +9 -1
  28. package/corpus/templates/clips/server/lib/builder-media-compression.ts +59 -7
  29. package/corpus/templates/clips/server/lib/media-verification-state.ts +17 -0
  30. package/corpus/templates/clips/server/routes/api/public-recording.get.ts +7 -0
  31. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/reset-chunks.post.ts +22 -1
  32. package/corpus/templates/plan/changelog/2026-07-17-diagrams-can-opt-into-a-polished-clean-rendering-mode-withou.md +6 -0
  33. package/corpus/templates/plan/shared/plan-content.ts +3 -0
  34. package/dist/client/blocks/library/diagram.config.d.ts +2 -0
  35. package/dist/client/blocks/library/diagram.config.d.ts.map +1 -1
  36. package/dist/client/blocks/library/diagram.config.js +8 -1
  37. package/dist/client/blocks/library/diagram.config.js.map +1 -1
  38. package/dist/client/blocks/library/diagram.d.ts.map +1 -1
  39. package/dist/client/blocks/library/diagram.js +9 -4
  40. package/dist/client/blocks/library/diagram.js.map +1 -1
  41. package/dist/collab/struct-routes.d.ts +1 -1
  42. package/dist/deploy/build.d.ts +3 -0
  43. package/dist/deploy/build.d.ts.map +1 -1
  44. package/dist/deploy/build.js +45 -1
  45. package/dist/deploy/build.js.map +1 -1
  46. package/dist/observability/routes.d.ts +5 -5
  47. package/dist/progress/routes.d.ts +1 -1
  48. package/dist/resources/handlers.d.ts +2 -2
  49. package/dist/secrets/routes.d.ts +9 -9
  50. package/docs/content/integrations.mdx +87 -253
  51. package/package.json +1 -1
@@ -78,6 +78,7 @@ import {
78
78
  createPauseTransitionQueue,
79
79
  type PauseTransitionQueue,
80
80
  } from "./pause-transition";
81
+ import { reconcileProcessingBackup } from "./processing-backup-recovery";
81
82
  import { buildCaptureTitle, type CaptureTitleResult } from "./recording-title";
82
83
  import { singleFlight } from "./single-flight";
83
84
  import {
@@ -86,6 +87,7 @@ import {
86
87
  type TranscriptionCapture,
87
88
  } from "./transcription-capture";
88
89
  import {
90
+ parseFinalizeReceipt,
89
91
  verifyFinalizeReceipt,
90
92
  type FinalizeReceipt,
91
93
  } from "./upload-verification";
@@ -744,6 +746,7 @@ export async function dismissBrowserRecordingBackup(
744
746
  async function markBrowserRecordingBackupError(
745
747
  recordingId: string,
746
748
  error: string,
749
+ incrementRetryCount = true,
747
750
  ): Promise<void> {
748
751
  const meta = await getBrowserRecordingBackupMeta(recordingId);
749
752
  if (!meta) return;
@@ -751,7 +754,26 @@ async function markBrowserRecordingBackupError(
751
754
  ...meta,
752
755
  lastAttemptAt: new Date().toISOString(),
753
756
  lastError: error,
754
- retryCount: meta.retryCount + 1,
757
+ retryCount: incrementRetryCount ? meta.retryCount + 1 : meta.retryCount,
758
+ });
759
+ }
760
+
761
+ const MEDIA_VERIFICATION_BACKUP_ERROR =
762
+ "The server could not finish verifying this upload. The local copy is still saved; retry to continue.";
763
+
764
+ async function flagBrowserBackupAfterProcessing(recordingId: string) {
765
+ await markBrowserRecordingBackupError(
766
+ recordingId,
767
+ MEDIA_VERIFICATION_BACKUP_ERROR,
768
+ false,
769
+ );
770
+ await emit("clips:pending-uploads-changed").catch(() => {});
771
+ }
772
+
773
+ async function flagNativeBackupAfterProcessing(recordingId: string) {
774
+ await invoke("native_fullscreen_recording_mark_upload_error", {
775
+ recordingId,
776
+ error: MEDIA_VERIFICATION_BACKUP_ERROR,
755
777
  });
756
778
  }
757
779
 
@@ -760,24 +782,26 @@ function scheduleBrowserBackupCleanupAfterProcessing(args: {
760
782
  recordingId: string;
761
783
  authToken?: string;
762
784
  }): void {
763
- void waitForReadyRecordingAfterFinalizeError({
764
- uploadUrl: chunkUrl(args.serverUrl, args.recordingId, 0, false),
765
- recordingId: args.recordingId,
766
- authToken: args.authToken,
767
- preferAuthenticated: true,
768
- timeoutMs: 12 * 60 * 1000,
769
- })
770
- .then((recovered) => {
771
- if (recovered?.status === "ready") {
772
- return deleteBrowserRecordingBackup(args.recordingId);
773
- }
774
- })
775
- .catch((err) => {
785
+ void reconcileProcessingBackup({
786
+ waitForReady: () =>
787
+ waitForReadyRecordingAfterFinalizeError({
788
+ uploadUrl: chunkUrl(args.serverUrl, args.recordingId, 0, false),
789
+ recordingId: args.recordingId,
790
+ authToken: args.authToken,
791
+ preferAuthenticated: true,
792
+ timeoutMs: 12 * 60 * 1000,
793
+ }),
794
+ onReady: () => deleteBrowserRecordingBackup(args.recordingId),
795
+ onUnresolved: () => flagBrowserBackupAfterProcessing(args.recordingId),
796
+ onPollError: (err) => {
776
797
  console.warn(
777
798
  "[clips-recorder] background backup cleanup check failed:",
778
799
  err,
779
800
  );
780
- });
801
+ },
802
+ }).catch((err) => {
803
+ console.warn("[clips-recorder] background backup flag failed:", err);
804
+ });
781
805
  }
782
806
 
783
807
  export function scheduleNativeBackupCleanupAfterProcessing(args: {
@@ -785,23 +809,26 @@ export function scheduleNativeBackupCleanupAfterProcessing(args: {
785
809
  recordingId: string;
786
810
  authToken?: string;
787
811
  }): void {
788
- void waitForReadyRecordingAfterFinalizeError({
789
- uploadUrl: chunkUrl(args.serverUrl, args.recordingId, 0, false),
790
- recordingId: args.recordingId,
791
- authToken: args.authToken,
792
- preferAuthenticated: true,
793
- timeoutMs: 12 * 60 * 1000,
794
- })
795
- .then((recovered) => {
796
- if (recovered?.status === "ready") {
797
- return invoke("native_fullscreen_recording_dismiss_upload", {
798
- recordingId: args.recordingId,
799
- });
800
- }
801
- })
802
- .catch((err) => {
812
+ void reconcileProcessingBackup({
813
+ waitForReady: () =>
814
+ waitForReadyRecordingAfterFinalizeError({
815
+ uploadUrl: chunkUrl(args.serverUrl, args.recordingId, 0, false),
816
+ recordingId: args.recordingId,
817
+ authToken: args.authToken,
818
+ preferAuthenticated: true,
819
+ timeoutMs: 12 * 60 * 1000,
820
+ }),
821
+ onReady: () =>
822
+ invoke("native_fullscreen_recording_dismiss_upload", {
823
+ recordingId: args.recordingId,
824
+ }),
825
+ onUnresolved: () => flagNativeBackupAfterProcessing(args.recordingId),
826
+ onPollError: (err) => {
803
827
  console.warn("[clips-recorder] native backup cleanup check failed:", err);
804
- });
828
+ },
829
+ }).catch((err) => {
830
+ console.warn("[clips-recorder] native backup flag failed:", err);
831
+ });
805
832
  }
806
833
 
807
834
  async function recoverAcceptedRecordingAfterFinalizeError({
@@ -884,7 +911,7 @@ async function postBackupChunk(
884
911
  `Upload retry failed (${res.status}): ${body.slice(0, 200)}`,
885
912
  );
886
913
  }
887
- return body ? (JSON.parse(body) as FinalizeReceipt) : null;
914
+ return parseFinalizeReceipt(body);
888
915
  }
889
916
 
890
917
  async function resetBrowserRecordingBackupUpload(
@@ -3965,9 +3992,7 @@ async function startRecordingInner(
3965
3992
  `Finalize failed (${finalRes.status}): ${bodyText.slice(0, 200)}`,
3966
3993
  );
3967
3994
  }
3968
- const receipt = bodyText
3969
- ? (JSON.parse(bodyText) as FinalizeReceipt)
3970
- : null;
3995
+ const receipt = parseFinalizeReceipt(bodyText);
3971
3996
  const receiptStatus = verifyFinalizeReceipt(receipt, {
3972
3997
  bytes: backupBytes,
3973
3998
  durationMs,
@@ -14,6 +14,18 @@ export interface LocalRecordingProof {
14
14
 
15
15
  export type FinalizeReceiptStatus = "processing" | "ready";
16
16
 
17
+ export function parseFinalizeReceipt(body: string): FinalizeReceipt | null {
18
+ if (!body) return null;
19
+ try {
20
+ const parsed: unknown = JSON.parse(body);
21
+ return parsed && typeof parsed === "object"
22
+ ? (parsed as FinalizeReceipt)
23
+ : null;
24
+ } catch {
25
+ throw new Error("Upload returned an invalid finalization response");
26
+ }
27
+ }
28
+
17
29
  export function verifyFinalizeReceipt(
18
30
  receipt: FinalizeReceipt | null,
19
31
  local: LocalRecordingProof,
@@ -141,6 +141,7 @@ pub fn run() {
141
141
  native_screen::native_fullscreen_recording_rotate_segment,
142
142
  native_screen::native_fullscreen_pending_uploads,
143
143
  native_screen::native_fullscreen_recording_retry_upload,
144
+ native_screen::native_fullscreen_recording_mark_upload_error,
144
145
  native_screen::native_fullscreen_recording_dismiss_upload,
145
146
  native_screen::native_fullscreen_open_drafts_folder,
146
147
  // local-only always-on screen memory compatibility helpers
@@ -2626,6 +2626,20 @@ pub async fn native_fullscreen_recording_retry_upload(
2626
2626
  }
2627
2627
  }
2628
2628
 
2629
+ #[tauri::command]
2630
+ pub async fn native_fullscreen_recording_mark_upload_error(
2631
+ app: AppHandle,
2632
+ recording_id: String,
2633
+ error: String,
2634
+ ) -> Result<(), String> {
2635
+ let mut saved = read_saved_recording_metadata(&app, &recording_id)?;
2636
+ saved.last_attempt_at = Some(now_iso());
2637
+ saved.last_error = Some(error);
2638
+ write_saved_recording_metadata(&app, &saved)?;
2639
+ let _ = app.emit("clips:pending-uploads-changed", ());
2640
+ Ok(())
2641
+ }
2642
+
2629
2643
  #[tauri::command]
2630
2644
  pub async fn native_fullscreen_recording_dismiss_upload(
2631
2645
  app: AppHandle,
@@ -586,7 +586,15 @@ export const dictations = table("clips_dictations", {
586
586
  // are text-only since native recognition runs on-device.
587
587
  audioUrl: text("audio_url"),
588
588
  source: text("source", {
589
- enum: ["fn-hold", "cmd-shift-space", "manual", "other", "fn", "custom"],
589
+ enum: [
590
+ "fn-hold",
591
+ "cmd-shift-space",
592
+ "manual",
593
+ "mobile",
594
+ "other",
595
+ "fn",
596
+ "custom",
597
+ ],
590
598
  })
591
599
  .notNull()
592
600
  .default("fn-hold"),
@@ -9,7 +9,7 @@ import {
9
9
  resolveBuilderPrivateKey,
10
10
  runWithRequestContext,
11
11
  } from "@agent-native/core/server";
12
- import { and, eq } from "drizzle-orm";
12
+ import { and, eq, isNull } from "drizzle-orm";
13
13
 
14
14
  import type { MediaWorkerCallback } from "../../shared/media-worker-contract.js";
15
15
  import { getDb, schema } from "../db/index.js";
@@ -958,7 +958,12 @@ export async function runBuilderMediaCompressionSweepOnce(): Promise<void> {
958
958
  key?: unknown;
959
959
  value?: unknown;
960
960
  }>) {
961
- const sessionId = typeof row.session_id === "string" ? row.session_id : "";
961
+ const sessionId =
962
+ typeof row.session_id === "string" ? row.session_id.trim() : "";
963
+ const key = typeof row.key === "string" ? row.key : "";
964
+ const recordingId = key.startsWith(BUILDER_MEDIA_COMPRESSION_STATE_PREFIX)
965
+ ? key.slice(BUILDER_MEDIA_COMPRESSION_STATE_PREFIX.length)
966
+ : "";
962
967
  const rawValue = typeof row.value === "string" ? row.value : "";
963
968
  let value: Record<string, unknown> | null = null;
964
969
  try {
@@ -967,14 +972,61 @@ export async function runBuilderMediaCompressionSweepOnce(): Promise<void> {
967
972
  continue;
968
973
  }
969
974
  const state = parseState(value);
970
- if (!state || isFinalStatus(state.status) || !isDue(state)) continue;
971
- const ownerEmail = state.ownerEmail || sessionId;
972
- if (!ownerEmail) continue;
975
+ if (
976
+ !sessionId ||
977
+ !recordingId ||
978
+ !state ||
979
+ state.recordingId !== recordingId ||
980
+ isFinalStatus(state.status) ||
981
+ !isDue(state)
982
+ ) {
983
+ continue;
984
+ }
973
985
  try {
986
+ const [recording] = await getDb()
987
+ .select({
988
+ ownerEmail: schema.recordings.ownerEmail,
989
+ orgId: schema.recordings.orgId,
990
+ videoUrl: schema.recordings.videoUrl,
991
+ })
992
+ .from(schema.recordings)
993
+ .where(
994
+ and(
995
+ eq(schema.recordings.id, recordingId),
996
+ ownerEmailMatches(schema.recordings.ownerEmail, sessionId),
997
+ eq(schema.recordings.status, "ready"),
998
+ isNull(schema.recordings.trashedAt),
999
+ ),
1000
+ )
1001
+ .limit(1);
1002
+ const expectedTarget = extractBuilderMediaTarget(
1003
+ recording?.videoUrl ?? "",
1004
+ );
1005
+ if (
1006
+ !recording ||
1007
+ !expectedTarget ||
1008
+ state.sourceUrl !== expectedTarget.sourceUrl ||
1009
+ (state.mediaWorker &&
1010
+ (state.mediaWorker.jobId !==
1011
+ mediaWorkerCompressionJobId(recordingId) ||
1012
+ state.mediaWorker.outputUrl !== expectedTarget.compressedUrl))
1013
+ ) {
1014
+ continue;
1015
+ }
1016
+
1017
+ const trustedState: BuilderMediaCompressionState = {
1018
+ ...state,
1019
+ ...expectedTarget,
1020
+ ownerEmail: recording.ownerEmail,
1021
+ orgId: recording.orgId,
1022
+ };
974
1023
  await runWithRequestContext(
975
- { userEmail: ownerEmail, orgId: state.orgId ?? undefined },
1024
+ {
1025
+ userEmail: recording.ownerEmail,
1026
+ orgId: recording.orgId ?? undefined,
1027
+ },
976
1028
  async () => {
977
- await processCompressionState({ ...state, ownerEmail });
1029
+ await processCompressionState(trustedState);
978
1030
  },
979
1031
  );
980
1032
  } catch (err) {
@@ -1,3 +1,5 @@
1
+ import { appStateGet } from "@agent-native/core/application-state";
2
+
1
3
  export const MEDIA_VERIFICATION_STATE_PREFIX = "recording-media-verification-";
2
4
 
3
5
  export type MediaVerificationMarker = {
@@ -36,3 +38,18 @@ export function parseMediaVerificationMarker(
36
38
  }
37
39
  return state as MediaVerificationMarker;
38
40
  }
41
+
42
+ export async function isMediaVerificationPending(args: {
43
+ ownerEmail: string;
44
+ recordingId: string;
45
+ recordingStatus: string;
46
+ }): Promise<boolean> {
47
+ if (args.recordingStatus !== "processing") return false;
48
+
49
+ const value = await appStateGet(
50
+ args.ownerEmail,
51
+ mediaVerificationStateKey(args.recordingId),
52
+ ).catch(() => null);
53
+ const marker = parseMediaVerificationMarker(value);
54
+ return marker?.recordingId === args.recordingId;
55
+ }
@@ -42,6 +42,7 @@ import {
42
42
  } from "../../../shared/transcript-segments.js";
43
43
  import { resolveTranscriptPresentation } from "../../../shared/transcript-status.js";
44
44
  import { getDb, schema } from "../../db/index.js";
45
+ import { isMediaVerificationPending } from "../../lib/media-verification-state.js";
45
46
  import { resolvePlayerThumbnailUrl } from "../../lib/player-thumbnail-url.js";
46
47
  import { resolvePlayerVideoUrl } from "../../lib/player-video-url.js";
47
48
  import {
@@ -398,6 +399,11 @@ export default defineEventHandler(async (event) => {
398
399
  // Referer of any outbound link the share page renders.
399
400
  setResponseHeader(event, "Referrer-Policy", "no-referrer");
400
401
  const transcriptPresentation = resolveTranscriptPresentation(transcript);
402
+ const verificationPending = await isMediaVerificationPending({
403
+ ownerEmail: rec.ownerEmail,
404
+ recordingId,
405
+ recordingStatus: rec.status,
406
+ });
401
407
 
402
408
  return {
403
409
  recording: {
@@ -416,6 +422,7 @@ export default defineEventHandler(async (event) => {
416
422
  hasAudio: Boolean(rec.hasAudio),
417
423
  hasCamera: Boolean(rec.hasCamera),
418
424
  status: rec.status,
425
+ verificationPending,
419
426
  uploadProgress: rec.uploadProgress,
420
427
  failureReason: rec.failureReason,
421
428
  // Don't leak the password to clients; just indicate whether one was set.
@@ -42,6 +42,7 @@ import {
42
42
  } from "h3";
43
43
 
44
44
  import { getDb, schema } from "../../../../db/index.js";
45
+ import { isMediaVerificationPending } from "../../../../lib/media-verification-state.js";
45
46
  import {
46
47
  getEventOwnerContext,
47
48
  ownerEmailMatches,
@@ -115,7 +116,11 @@ export default defineEventHandler(async (event: H3Event) => {
115
116
  const db = getDb();
116
117
 
117
118
  const [existing] = await db
118
- .select({ id: schema.recordings.id })
119
+ .select({
120
+ id: schema.recordings.id,
121
+ status: schema.recordings.status,
122
+ videoUrl: schema.recordings.videoUrl,
123
+ })
119
124
  .from(schema.recordings)
120
125
  .where(
121
126
  and(
@@ -129,6 +134,22 @@ export default defineEventHandler(async (event: H3Event) => {
129
134
  return { error: "Recording not found" };
130
135
  }
131
136
 
137
+ if (existing.status === "ready" && existing.videoUrl) {
138
+ setResponseStatus(event, 409);
139
+ return { error: "Recording is already ready" };
140
+ }
141
+
142
+ if (
143
+ await isMediaVerificationPending({
144
+ ownerEmail,
145
+ recordingId,
146
+ recordingStatus: existing.status,
147
+ })
148
+ ) {
149
+ setResponseStatus(event, 409);
150
+ return { error: "Recording is still being verified" };
151
+ }
152
+
132
153
  const cleared = await deleteAppStateByPrefix(
133
154
  `recording-chunks-${recordingId}-`,
134
155
  );
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: added
3
+ date: 2026-07-17
4
+ ---
5
+
6
+ Diagrams can opt into a polished clean rendering mode without sketch styling.
@@ -367,6 +367,8 @@ export type PlanDiagramBlock = PlanBlockBase & {
367
367
  */
368
368
  html?: string;
369
369
  css?: string;
370
+ /** `design` forces clean HTML/CSS rendering without the sketch overlay. */
371
+ renderMode?: PlanVisualCanvasMode;
370
372
  caption?: string;
371
373
  /** Outer surface frame. `auto` lets the host choose the right default. */
372
374
  frame?: PlanVisualFrame;
@@ -1396,6 +1398,7 @@ const diagramDataSchema: z.ZodType<PlanDiagramBlock["data"]> = z
1396
1398
  message: "Diagram css must not include document or script tags.",
1397
1399
  })
1398
1400
  .optional(),
1401
+ renderMode: visualCanvasModeSchema.optional(),
1399
1402
  caption: z.string().trim().max(600).optional(),
1400
1403
  frame: visualFrameSchema.optional(),
1401
1404
  nodes: z.array(diagramNodeSchema).max(80).optional(),
@@ -37,6 +37,8 @@ export interface DiagramData {
37
37
  */
38
38
  html?: string;
39
39
  css?: string;
40
+ /** `design` forces clean HTML/CSS rendering without the sketch overlay. */
41
+ renderMode?: "wireframe" | "design";
40
42
  caption?: string;
41
43
  /** Outer surface frame. `auto` lets the host choose the right default. */
42
44
  frame?: BlockVisualFrame;
@@ -1 +1 @@
1
- {"version":3,"file":"diagram.config.d.ts","sourceRoot":"","sources":["../../../../src/client/blocks/library/diagram.config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpE;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,CAAC,CAAC,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,CAAC,CAAC,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,0EAA0E;IAC1E,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB;;;OAGG;IACH,KAAK,CAAC,EAAE,WAAW,EAAE,CAAC;IACtB,KAAK,CAAC,EAAE,WAAW,EAAE,CAAC;IACtB,KAAK,CAAC,EAAE,WAAW,EAAE,CAAC;CACvB;AAsED;;;;GAIG;AACH,eAAO,MAAM,aAAa,EA+BP,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;AAiB1C;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,EAAE,cAAc,CAAC,WAAW,CAyBlD,CAAC"}
1
+ {"version":3,"file":"diagram.config.d.ts","sourceRoot":"","sources":["../../../../src/client/blocks/library/diagram.config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpE;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,CAAC,CAAC,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,CAAC,CAAC,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,2EAA2E;IAC3E,UAAU,CAAC,EAAE,WAAW,GAAG,QAAQ,CAAC;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,0EAA0E;IAC1E,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB;;;OAGG;IACH,KAAK,CAAC,EAAE,WAAW,EAAE,CAAC;IACtB,KAAK,CAAC,EAAE,WAAW,EAAE,CAAC;IACtB,KAAK,CAAC,EAAE,WAAW,EAAE,CAAC;CACvB;AAsED;;;;GAIG;AACH,eAAO,MAAM,aAAa,EAgCP,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;AAiB1C;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,EAAE,cAAc,CAAC,WAAW,CA+BlD,CAAC"}
@@ -78,6 +78,7 @@ export const diagramSchema = z
78
78
  message: "Diagram css must not include document or script tags.",
79
79
  })
80
80
  .optional(),
81
+ renderMode: z.enum(["wireframe", "design"]).optional(),
81
82
  caption: z.string().trim().max(600).optional(),
82
83
  frame: visualFrameSchema.optional(),
83
84
  nodes: z.array(diagramNodeSchema).max(80).optional(),
@@ -108,7 +109,7 @@ function graphDataForAttr(data) {
108
109
  return graph;
109
110
  if (hasChildFenceData(data))
110
111
  return undefined;
111
- const { frame: _frame, ...dataForAttr } = data;
112
+ const { frame: _frame, renderMode: _renderMode, ...dataForAttr } = data;
112
113
  return Object.keys(dataForAttr).length > 0 ? dataForAttr : undefined;
113
114
  }
114
115
  /**
@@ -125,6 +126,7 @@ export const diagramMdx = {
125
126
  data: graphDataForAttr(data),
126
127
  caption: data.caption,
127
128
  frame: data.frame,
129
+ renderMode: data.renderMode,
128
130
  }),
129
131
  fromAttrs: (attrs) => ({
130
132
  ...(attrs.object("data") ?? {}),
@@ -134,6 +136,11 @@ export const diagramMdx = {
134
136
  ...(attrs.string("frame") !== undefined
135
137
  ? { frame: attrs.string("frame") }
136
138
  : {}),
139
+ ...(attrs.string("renderMode") !== undefined
140
+ ? {
141
+ renderMode: attrs.string("renderMode"),
142
+ }
143
+ : {}),
137
144
  }),
138
145
  serializeChildren: (data) => serializeChildCodeFenceFields(data, { html: "html", css: "css" }),
139
146
  parseChildren: (childNodes) => childCodeFenceFields(childNodes, {
@@ -1 +1 @@
1
- {"version":3,"file":"diagram.config.js","sourceRoot":"","sources":["../../../../src/client/blocks/library/diagram.config.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from \"zod\";\n\nimport { childCodeFenceFields, serializeChildCodeFenceFields } from \"../mdx.js\";\nimport type { BlockMdxConfig, BlockVisualFrame } from \"../types.js\";\n\n/**\n * Pure (React-free) part of the shared `diagram` block: its data schema and MDX\n * round-trip config. Lives in core so BOTH apps' server/shared registries\n * (`plan-block-registry.ts`, `nfm-registry.ts`) and the client spec\n * (`diagram.tsx`) consume one definition. Keeping it React-free means importing\n * it into a server module never pulls React into the Nitro/SSR bundle.\n *\n * The MDX `tag` keeps backward compatibility with legacy\n * `<Diagram … data={…} />` files while the current authoring form stores\n * maintainable `html`/`css` as child code fences.\n */\n\nexport interface DiagramNode {\n id: string;\n label: string;\n detail?: string;\n x?: number;\n y?: number;\n}\n\nexport interface DiagramEdge {\n from: string;\n to: string;\n label?: string;\n}\n\nexport interface DiagramNote {\n id: string;\n text: string;\n x?: number;\n y?: number;\n}\n\nexport interface DiagramData {\n /**\n * Preferred authoring path for architecture/code diagrams: a scoped, inert\n * HTML/SVG fragment. Use .diagram-* primitives and --wf-* tokens; the\n * renderer supplies theme-token-backed styling plus sketch/clean style hooks.\n */\n html?: string;\n css?: string;\n caption?: string;\n /** Outer surface frame. `auto` lets the host choose the right default. */\n frame?: BlockVisualFrame;\n /**\n * Legacy compatibility path for older/simple node graphs. New plans should use\n * `html`/`css` when layout quality matters.\n */\n nodes?: DiagramNode[];\n edges?: DiagramEdge[];\n notes?: DiagramNote[];\n}\n\nconst idSchema = z.string().trim().min(1).max(120);\nconst unsafeDiagramHtmlPattern =\n /(?:<!doctype|<\\/?(?:html|head|body|script|style|iframe|object|embed|link|meta|base|form|math|foreignObject|noscript|frame|frameset|applet|portal|marquee)[\\s>/]|@(?:import|font-face|keyframes|page|namespace|charset)\\b|\\b(?:java\\s*script|vb\\s*script|data\\s*:\\s*(?:text\\/html|image\\/svg\\+xml))\\s*:?\\s*|\\bsrcdoc\\s*=|(?:^|\\s)(?:on[a-z][\\w:-]*|@[\\w:.-]+|x-on:[\\w:.-]+|:on[a-z][\\w:-]*|x-bind:on[a-z][\\w:-]*|:style|x-bind:style)\\s*=|expression\\s*\\(|url\\s*\\(\\s*['\"]?\\s*(?:java\\s*script|vb\\s*script|data\\s*:\\s*(?:text\\/html|image\\/svg\\+xml)))/i;\nconst unsafeViewportCssPattern =\n /(?:^|[;{\\s])position\\s*:\\s*(?:fixed|sticky)\\b|(?:^|[;{\\s])z-index\\s*:\\s*[1-9]\\d{4,}\\b/i;\n\nfunction decodeSafetyEntities(value: string): string {\n return value\n .replace(/&#(x[0-9a-f]+|\\d+);?/gi, (_, code: string) => {\n const point = code.toLowerCase().startsWith(\"x\")\n ? Number.parseInt(code.slice(1), 16)\n : Number.parseInt(code, 10);\n return Number.isFinite(point) ? String.fromCodePoint(point) : \"\";\n })\n .replace(/&(colon|tab|newline);/gi, (_, name: string) => {\n if (name.toLowerCase() === \"colon\") return \":\";\n if (name.toLowerCase() === \"tab\") return \"\\t\";\n return \"\\n\";\n });\n}\n\nfunction decodeCssSafetyEscapes(value: string): string {\n return value.replace(/\\\\([0-9a-fA-F]{1,6}\\s?|.)/g, (_match, escaped) => {\n const hex = String(escaped).match(/^[0-9a-fA-F]{1,6}/)?.[0];\n if (hex) {\n const point = Number.parseInt(hex, 16);\n return Number.isFinite(point) ? String.fromCodePoint(point) : \"\";\n }\n return String(escaped)[0] ?? \"\";\n });\n}\n\nfunction noActiveDiagramText(value: string) {\n const decoded = decodeCssSafetyEscapes(decodeSafetyEntities(value));\n const compact = decoded.toLowerCase().replace(/[\\u0000-\\u0020]+/g, \"\");\n return (\n !unsafeDiagramHtmlPattern.test(value) &&\n !unsafeDiagramHtmlPattern.test(decoded) &&\n !unsafeViewportCssPattern.test(decoded) &&\n !/(?:javascript|vbscript):|data:(?:text\\/html|image\\/svg\\+xml)|expression\\(|url\\(['\"]?(?:javascript|vbscript|data:(?:text\\/html|image\\/svg\\+xml))/.test(\n compact,\n )\n );\n}\n\nconst diagramNodeSchema = z.object({\n id: idSchema,\n label: z.string().trim().min(1).max(160),\n detail: z.string().trim().max(500).optional(),\n x: z.number().min(0).max(100).optional(),\n y: z.number().min(0).max(100).optional(),\n}) as z.ZodType<DiagramNode>;\n\nconst diagramEdgeSchema = z.object({\n from: idSchema,\n to: idSchema,\n label: z.string().trim().max(100).optional(),\n}) as z.ZodType<DiagramEdge>;\n\nconst diagramNoteSchema = z.object({\n id: idSchema,\n text: z.string().trim().min(1).max(500),\n x: z.number().min(0).max(100).optional(),\n y: z.number().min(0).max(100).optional(),\n}) as z.ZodType<DiagramNote>;\n\nconst visualFrameSchema = z.enum([\"auto\", \"show\", \"hide\"]);\n\n/**\n * The block can be a flexible HTML/SVG fragment or a legacy positional\n * node/edge/note graph, so it ships a custom `Edit` rather than relying on the\n * schema auto-editor. Editing stays comment/patch-driven.\n */\nexport const diagramSchema = z\n .object({\n html: z\n .string()\n .trim()\n .max(100_000)\n .refine(noActiveDiagramText, {\n message:\n \"Diagram html must be an inert fragment; SVG is allowed, scripts/events are not.\",\n })\n .optional(),\n css: z\n .string()\n .max(50_000)\n .refine(noActiveDiagramText, {\n message: \"Diagram css must not include document or script tags.\",\n })\n .optional(),\n caption: z.string().trim().max(600).optional(),\n frame: visualFrameSchema.optional(),\n nodes: z.array(diagramNodeSchema).max(80).optional(),\n edges: z.array(diagramEdgeSchema).max(120).optional(),\n notes: z.array(diagramNoteSchema).max(40).optional(),\n })\n .superRefine((data, ctx) => {\n if (data.html?.trim() || (data.nodes?.length ?? 0) > 0) return;\n ctx.addIssue({\n code: \"custom\",\n path: [\"html\"],\n message: \"Diagram block requires html or at least one node.\",\n });\n }) as unknown as z.ZodType<DiagramData>;\n\nfunction hasChildFenceData(data: DiagramData) {\n return Boolean(data.html || data.css);\n}\n\nfunction graphDataForAttr(data: DiagramData): DiagramData | undefined {\n const graph: DiagramData = {};\n if (data.nodes?.length) graph.nodes = data.nodes;\n if (data.edges?.length) graph.edges = data.edges;\n if (data.notes?.length) graph.notes = data.notes;\n if (Object.keys(graph).length > 0) return graph;\n if (hasChildFenceData(data)) return undefined;\n const { frame: _frame, ...dataForAttr } = data;\n return Object.keys(dataForAttr).length > 0 ? dataForAttr : undefined;\n}\n\n/**\n * MDX config: new source uses normal fenced-code children:\n *\n * `<Diagram caption=\"...\">` plus child `html` / `css` fences. Legacy\n * `<Diagram data={...} />` remains accepted by `fromAttrs`; `toAttrs` keeps the\n * `data` key present-but-undefined when using child fences so docs validation\n * still recognizes old `data={...}` as a supported compatibility attribute.\n */\nexport const diagramMdx: BlockMdxConfig<DiagramData> = {\n tag: \"Diagram\",\n toAttrs: (data) => ({\n data: graphDataForAttr(data) as unknown as\n | Record<string, unknown>\n | undefined,\n caption: data.caption,\n frame: data.frame,\n }),\n fromAttrs: (attrs) => ({\n ...(attrs.object<DiagramData>(\"data\") ?? {}),\n ...(attrs.string(\"caption\") !== undefined\n ? { caption: attrs.string(\"caption\") }\n : {}),\n ...(attrs.string(\"frame\") !== undefined\n ? { frame: attrs.string(\"frame\") as BlockVisualFrame }\n : {}),\n }),\n serializeChildren: (data) =>\n serializeChildCodeFenceFields(data, { html: \"html\", css: \"css\" }),\n parseChildren: (childNodes) =>\n childCodeFenceFields<DiagramData>(childNodes, {\n html: \"html\",\n css: \"css\",\n }),\n};\n"]}
1
+ {"version":3,"file":"diagram.config.js","sourceRoot":"","sources":["../../../../src/client/blocks/library/diagram.config.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from \"zod\";\n\nimport { childCodeFenceFields, serializeChildCodeFenceFields } from \"../mdx.js\";\nimport type { BlockMdxConfig, BlockVisualFrame } from \"../types.js\";\n\n/**\n * Pure (React-free) part of the shared `diagram` block: its data schema and MDX\n * round-trip config. Lives in core so BOTH apps' server/shared registries\n * (`plan-block-registry.ts`, `nfm-registry.ts`) and the client spec\n * (`diagram.tsx`) consume one definition. Keeping it React-free means importing\n * it into a server module never pulls React into the Nitro/SSR bundle.\n *\n * The MDX `tag` keeps backward compatibility with legacy\n * `<Diagram … data={…} />` files while the current authoring form stores\n * maintainable `html`/`css` as child code fences.\n */\n\nexport interface DiagramNode {\n id: string;\n label: string;\n detail?: string;\n x?: number;\n y?: number;\n}\n\nexport interface DiagramEdge {\n from: string;\n to: string;\n label?: string;\n}\n\nexport interface DiagramNote {\n id: string;\n text: string;\n x?: number;\n y?: number;\n}\n\nexport interface DiagramData {\n /**\n * Preferred authoring path for architecture/code diagrams: a scoped, inert\n * HTML/SVG fragment. Use .diagram-* primitives and --wf-* tokens; the\n * renderer supplies theme-token-backed styling plus sketch/clean style hooks.\n */\n html?: string;\n css?: string;\n /** `design` forces clean HTML/CSS rendering without the sketch overlay. */\n renderMode?: \"wireframe\" | \"design\";\n caption?: string;\n /** Outer surface frame. `auto` lets the host choose the right default. */\n frame?: BlockVisualFrame;\n /**\n * Legacy compatibility path for older/simple node graphs. New plans should use\n * `html`/`css` when layout quality matters.\n */\n nodes?: DiagramNode[];\n edges?: DiagramEdge[];\n notes?: DiagramNote[];\n}\n\nconst idSchema = z.string().trim().min(1).max(120);\nconst unsafeDiagramHtmlPattern =\n /(?:<!doctype|<\\/?(?:html|head|body|script|style|iframe|object|embed|link|meta|base|form|math|foreignObject|noscript|frame|frameset|applet|portal|marquee)[\\s>/]|@(?:import|font-face|keyframes|page|namespace|charset)\\b|\\b(?:java\\s*script|vb\\s*script|data\\s*:\\s*(?:text\\/html|image\\/svg\\+xml))\\s*:?\\s*|\\bsrcdoc\\s*=|(?:^|\\s)(?:on[a-z][\\w:-]*|@[\\w:.-]+|x-on:[\\w:.-]+|:on[a-z][\\w:-]*|x-bind:on[a-z][\\w:-]*|:style|x-bind:style)\\s*=|expression\\s*\\(|url\\s*\\(\\s*['\"]?\\s*(?:java\\s*script|vb\\s*script|data\\s*:\\s*(?:text\\/html|image\\/svg\\+xml)))/i;\nconst unsafeViewportCssPattern =\n /(?:^|[;{\\s])position\\s*:\\s*(?:fixed|sticky)\\b|(?:^|[;{\\s])z-index\\s*:\\s*[1-9]\\d{4,}\\b/i;\n\nfunction decodeSafetyEntities(value: string): string {\n return value\n .replace(/&#(x[0-9a-f]+|\\d+);?/gi, (_, code: string) => {\n const point = code.toLowerCase().startsWith(\"x\")\n ? Number.parseInt(code.slice(1), 16)\n : Number.parseInt(code, 10);\n return Number.isFinite(point) ? String.fromCodePoint(point) : \"\";\n })\n .replace(/&(colon|tab|newline);/gi, (_, name: string) => {\n if (name.toLowerCase() === \"colon\") return \":\";\n if (name.toLowerCase() === \"tab\") return \"\\t\";\n return \"\\n\";\n });\n}\n\nfunction decodeCssSafetyEscapes(value: string): string {\n return value.replace(/\\\\([0-9a-fA-F]{1,6}\\s?|.)/g, (_match, escaped) => {\n const hex = String(escaped).match(/^[0-9a-fA-F]{1,6}/)?.[0];\n if (hex) {\n const point = Number.parseInt(hex, 16);\n return Number.isFinite(point) ? String.fromCodePoint(point) : \"\";\n }\n return String(escaped)[0] ?? \"\";\n });\n}\n\nfunction noActiveDiagramText(value: string) {\n const decoded = decodeCssSafetyEscapes(decodeSafetyEntities(value));\n const compact = decoded.toLowerCase().replace(/[\\u0000-\\u0020]+/g, \"\");\n return (\n !unsafeDiagramHtmlPattern.test(value) &&\n !unsafeDiagramHtmlPattern.test(decoded) &&\n !unsafeViewportCssPattern.test(decoded) &&\n !/(?:javascript|vbscript):|data:(?:text\\/html|image\\/svg\\+xml)|expression\\(|url\\(['\"]?(?:javascript|vbscript|data:(?:text\\/html|image\\/svg\\+xml))/.test(\n compact,\n )\n );\n}\n\nconst diagramNodeSchema = z.object({\n id: idSchema,\n label: z.string().trim().min(1).max(160),\n detail: z.string().trim().max(500).optional(),\n x: z.number().min(0).max(100).optional(),\n y: z.number().min(0).max(100).optional(),\n}) as z.ZodType<DiagramNode>;\n\nconst diagramEdgeSchema = z.object({\n from: idSchema,\n to: idSchema,\n label: z.string().trim().max(100).optional(),\n}) as z.ZodType<DiagramEdge>;\n\nconst diagramNoteSchema = z.object({\n id: idSchema,\n text: z.string().trim().min(1).max(500),\n x: z.number().min(0).max(100).optional(),\n y: z.number().min(0).max(100).optional(),\n}) as z.ZodType<DiagramNote>;\n\nconst visualFrameSchema = z.enum([\"auto\", \"show\", \"hide\"]);\n\n/**\n * The block can be a flexible HTML/SVG fragment or a legacy positional\n * node/edge/note graph, so it ships a custom `Edit` rather than relying on the\n * schema auto-editor. Editing stays comment/patch-driven.\n */\nexport const diagramSchema = z\n .object({\n html: z\n .string()\n .trim()\n .max(100_000)\n .refine(noActiveDiagramText, {\n message:\n \"Diagram html must be an inert fragment; SVG is allowed, scripts/events are not.\",\n })\n .optional(),\n css: z\n .string()\n .max(50_000)\n .refine(noActiveDiagramText, {\n message: \"Diagram css must not include document or script tags.\",\n })\n .optional(),\n renderMode: z.enum([\"wireframe\", \"design\"]).optional(),\n caption: z.string().trim().max(600).optional(),\n frame: visualFrameSchema.optional(),\n nodes: z.array(diagramNodeSchema).max(80).optional(),\n edges: z.array(diagramEdgeSchema).max(120).optional(),\n notes: z.array(diagramNoteSchema).max(40).optional(),\n })\n .superRefine((data, ctx) => {\n if (data.html?.trim() || (data.nodes?.length ?? 0) > 0) return;\n ctx.addIssue({\n code: \"custom\",\n path: [\"html\"],\n message: \"Diagram block requires html or at least one node.\",\n });\n }) as unknown as z.ZodType<DiagramData>;\n\nfunction hasChildFenceData(data: DiagramData) {\n return Boolean(data.html || data.css);\n}\n\nfunction graphDataForAttr(data: DiagramData): DiagramData | undefined {\n const graph: DiagramData = {};\n if (data.nodes?.length) graph.nodes = data.nodes;\n if (data.edges?.length) graph.edges = data.edges;\n if (data.notes?.length) graph.notes = data.notes;\n if (Object.keys(graph).length > 0) return graph;\n if (hasChildFenceData(data)) return undefined;\n const { frame: _frame, renderMode: _renderMode, ...dataForAttr } = data;\n return Object.keys(dataForAttr).length > 0 ? dataForAttr : undefined;\n}\n\n/**\n * MDX config: new source uses normal fenced-code children:\n *\n * `<Diagram caption=\"...\">` plus child `html` / `css` fences. Legacy\n * `<Diagram data={...} />` remains accepted by `fromAttrs`; `toAttrs` keeps the\n * `data` key present-but-undefined when using child fences so docs validation\n * still recognizes old `data={...}` as a supported compatibility attribute.\n */\nexport const diagramMdx: BlockMdxConfig<DiagramData> = {\n tag: \"Diagram\",\n toAttrs: (data) => ({\n data: graphDataForAttr(data) as unknown as\n | Record<string, unknown>\n | undefined,\n caption: data.caption,\n frame: data.frame,\n renderMode: data.renderMode,\n }),\n fromAttrs: (attrs) => ({\n ...(attrs.object<DiagramData>(\"data\") ?? {}),\n ...(attrs.string(\"caption\") !== undefined\n ? { caption: attrs.string(\"caption\") }\n : {}),\n ...(attrs.string(\"frame\") !== undefined\n ? { frame: attrs.string(\"frame\") as BlockVisualFrame }\n : {}),\n ...(attrs.string(\"renderMode\") !== undefined\n ? {\n renderMode: attrs.string(\"renderMode\") as DiagramData[\"renderMode\"],\n }\n : {}),\n }),\n serializeChildren: (data) =>\n serializeChildCodeFenceFields(data, { html: \"html\", css: \"css\" }),\n parseChildren: (childNodes) =>\n childCodeFenceFields<DiagramData>(childNodes, {\n html: \"html\",\n css: \"css\",\n }),\n};\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"diagram.d.ts","sourceRoot":"","sources":["../../../../src/client/blocks/library/diagram.tsx"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EACV,cAAc,EACd,cAAc,EAEf,MAAM,aAAa,CAAC;AAErB,OAAO,EAGL,KAAK,WAAW,EAGjB,MAAM,qBAAqB,CAAC;AAkiB7B;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,EAC9B,QAAQ,EACR,OAAO,GACR,EAAE;IACD,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,+BAsCA;AAoFD,iFAAiF;AACjF,wBAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,OAAO,EACP,KAAK,EACL,OAAO,EACP,GAAG,EACH,cAAc,GACf,EAAE,cAAc,CAAC,WAAW,CAAC,+BAY7B;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,QAAQ,EACR,QAAQ,EACR,OAAO,EACP,KAAK,EACL,OAAO,EACP,GAAG,GACJ,EAAE,cAAc,CAAC,WAAW,CAAC,+BAkL7B;AAED,kFAAkF;AAClF,eAAO,MAAM,YAAY,8CAgBvB,CAAC"}
1
+ {"version":3,"file":"diagram.d.ts","sourceRoot":"","sources":["../../../../src/client/blocks/library/diagram.tsx"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EACV,cAAc,EACd,cAAc,EAEf,MAAM,aAAa,CAAC;AAErB,OAAO,EAGL,KAAK,WAAW,EAGjB,MAAM,qBAAqB,CAAC;AAoiB7B;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,EAC9B,QAAQ,EACR,OAAO,GACR,EAAE;IACD,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,+BAsCA;AAqFD,iFAAiF;AACjF,wBAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,OAAO,EACP,KAAK,EACL,OAAO,EACP,GAAG,EACH,cAAc,GACf,EAAE,cAAc,CAAC,WAAW,CAAC,+BAY7B;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,QAAQ,EACR,QAAQ,EACR,OAAO,EACP,KAAK,EACL,OAAO,EACP,GAAG,GACJ,EAAE,cAAc,CAAC,WAAW,CAAC,+BAoL7B;AAED,kFAAkF;AAClF,eAAO,MAAM,YAAY,8CAgBvB,CAAC"}
@@ -45,7 +45,9 @@ const DIAGRAM_ROUGH_SELECTOR = "[data-rough],.diagram-panel,.diagram-node,.diagr
45
45
  function HtmlDiagram({ data, ctx, compact, }) {
46
46
  const ref = useRef(null);
47
47
  const isDark = useIsDark();
48
- const style = useWireframeStyle();
48
+ const preferredStyle = useWireframeStyle();
49
+ const designMode = data.renderMode === "design";
50
+ const style = designMode ? "clean" : preferredStyle;
49
51
  const showFrame = resolveVisualFrame(data.frame, ctx);
50
52
  const scopeId = useId().replace(/[^a-zA-Z0-9_-]/g, "");
51
53
  // Sanitize author HTML/CSS at the render point (defense-in-depth against
@@ -61,7 +63,7 @@ function HtmlDiagram({ data, ctx, compact, }) {
61
63
  ? scopeDesignCss(safeCss, `[data-plan-diagram-scope="${scopeId}"]`)
62
64
  : "";
63
65
  }, [data.css, scopeId]);
64
- return (_jsxs("div", { ref: ref, className: cn("plan-diagram-shell", compact && "plan-diagram-compact"), children: [_jsxs("div", { className: "plan-diagram-frame", dir: ctx.textDirection, "data-theme": isDark ? "dark" : "light", "data-style": style, "data-frame": showFrame ? "show" : "hide", "data-text-direction": ctx.textDirection, "data-plan-diagram-scope": scopeId, children: [scopedCss && _jsx("style", { children: scopedCss }), _jsx("div", { className: "plan-diagram-frame-content", dangerouslySetInnerHTML: { __html: safeHtml } })] }), _jsx(RoughOverlay, { scopeRef: ref, enabled: style === "sketchy", drawFrame: showFrame, selector: DIAGRAM_ROUGH_SELECTOR }), data.caption && !compact && (_jsx("p", { className: "mt-3 text-sm leading-6 text-muted-foreground", children: data.caption }))] }));
66
+ return (_jsxs("div", { ref: ref, className: cn("plan-diagram-shell", compact && "plan-diagram-compact"), children: [_jsxs("div", { className: "plan-diagram-frame", dir: ctx.textDirection, "data-theme": isDark ? "dark" : "light", "data-style": style, "data-frame": showFrame ? "show" : "hide", "data-text-direction": ctx.textDirection, "data-plan-diagram-scope": scopeId, children: [scopedCss && _jsx("style", { children: scopedCss }), _jsx("div", { className: "plan-diagram-frame-content", dangerouslySetInnerHTML: { __html: safeHtml } })] }), _jsx(RoughOverlay, { scopeRef: ref, enabled: !designMode && style === "sketchy", drawFrame: showFrame, selector: DIAGRAM_ROUGH_SELECTOR }), data.caption && !compact && (_jsx("p", { className: "mt-3 text-sm leading-6 text-muted-foreground", children: data.caption }))] }));
65
67
  }
66
68
  function resolveVisualFrame(frame, ctx) {
67
69
  const resolved = frame && frame !== "auto" ? frame : (ctx.visualFrame ?? "show");
@@ -282,7 +284,8 @@ export function DiagramLightbox({ children, onClose, }) {
282
284
  */
283
285
  function ExpandableDiagramBody({ data, ctx, compact, }) {
284
286
  const [expanded, setExpanded] = useState(false);
285
- const supportsStyleToggle = Boolean(data.html);
287
+ const designMode = data.renderMode === "design";
288
+ const supportsStyleToggle = Boolean(data.html) && !designMode;
286
289
  const style = useWireframeStyle();
287
290
  const copy = useBlockCopy();
288
291
  const sketchy = style === "sketchy";
@@ -336,7 +339,9 @@ export function DiagramEdit({ data, onChange, editable, blockId, title, summary,
336
339
  onChange({
337
340
  html: html.trim() || undefined,
338
341
  css: css.trim() || undefined,
342
+ renderMode: data.renderMode,
339
343
  caption: caption.trim() || undefined,
344
+ frame: data.frame,
340
345
  nodes: data.nodes,
341
346
  edges: data.edges,
342
347
  notes: data.notes,
@@ -388,7 +393,7 @@ export const diagramBlock = defineBlock({
388
393
  // edit from a corner button + panel rather than inline.
389
394
  editSurface: "panel",
390
395
  label: "Diagram",
391
- description: "A flexible inline architecture/code diagram. Prefer html/css with SVG or semantic HTML for polished two-dimensional layouts; use .diagram-* primitives and --wf-* tokens for theme/sketch compatibility. Legacy nodes/edges are only for simple previews.",
396
+ description: "A flexible inline architecture/code diagram. Prefer html/css with SVG or semantic HTML for polished two-dimensional layouts; use renderMode design for clean presentation or .diagram-* primitives and --wf-* tokens for theme/sketch compatibility. Legacy nodes/edges are only for simple previews.",
392
397
  // Seed the legacy fallback shape so a fresh block validates while agents can
393
398
  // replace it with html/css when layout quality matters.
394
399
  empty: () => ({ nodes: [{ id: "n1", label: "Module" }], edges: [] }),