@@ -5,6 +5,7 @@ import getBlockUidByTextOnPage from "roamjs-components/queries/getBlockUidByText
55import getPageUidByPageTitle from "roamjs-components/queries/getPageUidByPageTitle" ;
66import { createBlock } from "roamjs-components/writes" ;
77import { getSetting , setSetting } from "~/utils/extensionSettings" ;
8+ import internalError from "~/utils/internalError" ;
89import {
910 readAllLegacyFeatureFlags ,
1011 readAllLegacyGlobalSettings ,
@@ -26,6 +27,7 @@ import type { z } from "zod";
2627const LOG_PREFIX = "[DG BlockProps Migration]" ;
2728const GRAPH_MIGRATION_MARKER = "Block props migrated" ;
2829const PERSONAL_MIGRATION_MARKER = "dg-personal-settings-migrated" ;
30+ const MAX_ERROR_CONTEXT_LENGTH = 5000 ;
2931
3032const hasGraphMigrationMarker = ( ) : boolean =>
3133 ! ! getBlockUidByTextOnPage ( {
@@ -39,6 +41,14 @@ const isPropsValid = (
3941) : boolean =>
4042 ! ! props && Object . keys ( props ) . length > 0 && schema . safeParse ( props ) . success ;
4143
44+ const serializeErrorContext = ( value : unknown ) : string => {
45+ try {
46+ return JSON . stringify ( value ) . slice ( 0 , MAX_ERROR_CONTEXT_LENGTH ) ;
47+ } catch {
48+ return String ( value ) ;
49+ }
50+ } ;
51+
4252const shouldWrite = (
4353 schema : z . ZodTypeAny ,
4454 currentProps : Record < string , json > | null ,
@@ -48,11 +58,6 @@ const shouldWrite = (
4858 return true ;
4959 }
5060
51- // Compare Zod-normalized parsed legacy against current props directly.
52- // Safe on retry: if prior run already wrote parsedLegacy, they'll match
53- // → skip. If user edited via settings UI, dual-write keeps both sides in
54- // sync → match → skip. The only write happens when legacy genuinely
55- // differs from current (first migration or tree-only edit).
5661 return JSON . stringify ( parsedLegacy ) !== JSON . stringify ( currentProps ) ;
5762} ;
5863
@@ -71,9 +76,6 @@ const migrateSection = ({
7176
7277 const parseResult = schema . safeParse ( legacyData ) ;
7378 if ( ! parseResult . success ) {
74- // Legacy malformed — succeed if current props are already valid.
75- // migrateGraphLevel runs before initDiscourseNodePages, so valid props
76- // at this point were written by a prior migration run, not init-seeded.
7779 if ( isPropsValid ( schema , currentProps ) ) {
7880 console . log (
7981 `${ LOG_PREFIX } ${ label } : legacy malformed but props already valid, skipping` ,
@@ -83,6 +85,17 @@ const migrateSection = ({
8385 console . warn ( `${ LOG_PREFIX } ${ label } : Zod validation failed, skipping` , {
8486 error : parseResult . error . message ,
8587 } ) ;
88+ internalError ( {
89+ error : parseResult . error ,
90+ type : "DG Block Props Migration" ,
91+ context : {
92+ label,
93+ blockUid,
94+ legacyData : serializeErrorContext ( legacyData ) ,
95+ currentProps : serializeErrorContext ( currentProps ) ,
96+ } ,
97+ sendEmail : false ,
98+ } ) ;
8699 return false ;
87100 }
88101
@@ -117,8 +130,6 @@ const migrateDiscourseNodes = (): boolean => {
117130 nodeText ,
118131 ) ;
119132 if ( ! legacyData ) {
120- // Legacy unreadable — if current props are already valid, treat as
121- // success so a missing/malformed legacy tree doesn't block the marker.
122133 if ( isPropsValid ( DiscourseNodeSchema , getBlockProps ( nodePageUid ) ) ) {
123134 console . log (
124135 `${ LOG_PREFIX } Discourse Node (${ nodeText } ): legacy unreadable but props already valid, skipping` ,
@@ -128,6 +139,16 @@ const migrateDiscourseNodes = (): boolean => {
128139 console . warn (
129140 `${ LOG_PREFIX } Discourse Node (${ nodeText } ): legacy data unreadable` ,
130141 ) ;
142+ internalError ( {
143+ error : `Legacy discourse node data unreadable: ${ nodeText } ` ,
144+ type : "DG Block Props Migration" ,
145+ context : {
146+ label : `Discourse Node (${ nodeText } )` ,
147+ blockUid : nodePageUid ,
148+ currentProps : serializeErrorContext ( getBlockProps ( nodePageUid ) ) ,
149+ } ,
150+ sendEmail : false ,
151+ } ) ;
131152 allOk = false ;
132153 continue ;
133154 }
@@ -151,7 +172,15 @@ export const migrateGraphLevel = async (
151172 blockUids : Record < string , string > ,
152173) : Promise < void > => {
153174 const pageUid = getPageUidByPageTitle ( DG_BLOCK_PROP_SETTINGS_PAGE_TITLE ) ;
154- if ( ! pageUid ) return ;
175+ if ( ! pageUid ) {
176+ internalError ( {
177+ error : `Settings page not found: ${ DG_BLOCK_PROP_SETTINGS_PAGE_TITLE } ` ,
178+ type : "DG Block Props Migration" ,
179+ context : { scope : "graph" } ,
180+ sendEmail : false ,
181+ } ) ;
182+ return ;
183+ }
155184
156185 if ( hasGraphMigrationMarker ( ) ) {
157186 console . log ( `${ LOG_PREFIX } graph-level: skipped (already migrated)` ) ;
@@ -160,9 +189,19 @@ export const migrateGraphLevel = async (
160189
161190 let failures = 0 ;
162191
163- // Feature flags
164192 const featureFlagUid = blockUids [ TOP_LEVEL_BLOCK_PROP_KEYS . featureFlags ] ;
165- if ( featureFlagUid ) {
193+ if ( ! featureFlagUid ) {
194+ internalError ( {
195+ error : `Missing block uid for ${ TOP_LEVEL_BLOCK_PROP_KEYS . featureFlags } ` ,
196+ type : "DG Block Props Migration" ,
197+ context : {
198+ scope : "graph" ,
199+ blockUids : serializeErrorContext ( blockUids ) ,
200+ } ,
201+ sendEmail : false ,
202+ } ) ;
203+ failures ++ ;
204+ } else {
166205 const legacyFlags = readAllLegacyFeatureFlags ( ) ;
167206 if (
168207 ! migrateSection ( {
@@ -176,9 +215,19 @@ export const migrateGraphLevel = async (
176215 }
177216 }
178217
179- // Global settings
180218 const globalUid = blockUids [ TOP_LEVEL_BLOCK_PROP_KEYS . global ] ;
181- if ( globalUid ) {
219+ if ( ! globalUid ) {
220+ internalError ( {
221+ error : `Missing block uid for ${ TOP_LEVEL_BLOCK_PROP_KEYS . global } ` ,
222+ type : "DG Block Props Migration" ,
223+ context : {
224+ scope : "graph" ,
225+ blockUids : serializeErrorContext ( blockUids ) ,
226+ } ,
227+ sendEmail : false ,
228+ } ) ;
229+ failures ++ ;
230+ } else {
182231 const legacyGlobal = readAllLegacyGlobalSettings ( ) ;
183232 if (
184233 ! migrateSection ( {
@@ -192,7 +241,6 @@ export const migrateGraphLevel = async (
192241 }
193242 }
194243
195- // Discourse nodes
196244 if ( ! migrateDiscourseNodes ( ) ) {
197245 failures ++ ;
198246 }
@@ -231,6 +279,16 @@ export const migratePersonalSettings = async (
231279 console . warn (
232280 `${ LOG_PREFIX } personal: block not found for key "${ personalKey } ", skipping` ,
233281 ) ;
282+ internalError ( {
283+ error : `Missing personal settings block for key "${ personalKey } "` ,
284+ type : "DG Block Props Migration" ,
285+ context : {
286+ scope : "personal" ,
287+ personalKey,
288+ blockUids : serializeErrorContext ( blockUids ) ,
289+ } ,
290+ sendEmail : false ,
291+ } ) ;
234292 return ;
235293 }
236294
0 commit comments