@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.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +7 -0
- package/corpus/core/docs/content/integrations.mdx +87 -253
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/client/blocks/library/diagram.config.ts +10 -1
- package/corpus/core/src/client/blocks/library/diagram.tsx +9 -4
- package/corpus/core/src/deploy/build.ts +63 -3
- package/corpus/templates/clips/.agents/skills/dictate/SKILL.md +28 -5
- package/corpus/templates/clips/.agents/skills/meetings/SKILL.md +7 -0
- package/corpus/templates/clips/.agents/skills/recording/SKILL.md +23 -0
- package/corpus/templates/clips/AGENTS.md +10 -0
- package/corpus/templates/clips/actions/create-dictation.ts +29 -4
- package/corpus/templates/clips/actions/finalize-recording.ts +2 -4
- package/corpus/templates/clips/actions/get-recording-player-data.ts +16 -6
- package/corpus/templates/clips/actions/set-mobile-capture-state.ts +37 -0
- package/corpus/templates/clips/actions/update-dictation.ts +9 -1
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +13 -2
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +13 -2
- package/corpus/templates/clips/changelog/2026-07-17-capture-dictation-background-meeting-audio-and-camera-videos.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-delayed-upload-verification-keeps-local-backups-retryable.md +6 -0
- package/corpus/templates/clips/desktop/src/app.tsx +17 -0
- package/corpus/templates/clips/desktop/src/lib/processing-backup-recovery.ts +19 -0
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +60 -35
- package/corpus/templates/clips/desktop/src/lib/upload-verification.ts +12 -0
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +1 -0
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +14 -0
- package/corpus/templates/clips/server/db/schema.ts +9 -1
- package/corpus/templates/clips/server/lib/builder-media-compression.ts +59 -7
- package/corpus/templates/clips/server/lib/media-verification-state.ts +17 -0
- package/corpus/templates/clips/server/routes/api/public-recording.get.ts +7 -0
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/reset-chunks.post.ts +22 -1
- package/corpus/templates/plan/changelog/2026-07-17-diagrams-can-opt-into-a-polished-clean-rendering-mode-withou.md +6 -0
- package/corpus/templates/plan/shared/plan-content.ts +3 -0
- package/dist/client/blocks/library/diagram.config.d.ts +2 -0
- package/dist/client/blocks/library/diagram.config.d.ts.map +1 -1
- package/dist/client/blocks/library/diagram.config.js +8 -1
- package/dist/client/blocks/library/diagram.config.js.map +1 -1
- package/dist/client/blocks/library/diagram.d.ts.map +1 -1
- package/dist/client/blocks/library/diagram.js +9 -4
- package/dist/client/blocks/library/diagram.js.map +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/deploy/build.d.ts +3 -0
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +45 -1
- package/dist/deploy/build.js.map +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/secrets/routes.d.ts +9 -9
- package/docs/content/integrations.mdx +87 -253
- 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
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
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
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
|
|
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
|
|
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: [
|
|
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 =
|
|
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 (
|
|
971
|
-
|
|
972
|
-
|
|
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
|
-
{
|
|
1024
|
+
{
|
|
1025
|
+
userEmail: recording.ownerEmail,
|
|
1026
|
+
orgId: recording.orgId ?? undefined,
|
|
1027
|
+
},
|
|
976
1028
|
async () => {
|
|
977
|
-
await processCompressionState(
|
|
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({
|
|
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
|
);
|
|
@@ -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,
|
|
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;
|
|
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
|
|
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
|
|
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: [] }),
|