Skip to content

Commit deb4c5b

Browse files
committed
fix: decode file-storage data URLs at read sites instead of @Property transforms
The ad4m Rust executor silently drops JS-function @Property transforms. Properties with resolveLanguage: FILE_STORAGE_LANGUAGE always arrive as data URI strings (data:<mime>;base64,<b64>), never as decoded values. - CollectionBlock.editorState: remove transform, type string | null - Template.schema: remove decodeFileAsJson transform, type string | null - Theme.css / Theme.overrides: remove both transforms, type string | null - TemplateStore: decode template.schema via decodeFileAsJson() at read time (fixes silent skip of all custom templates - typeof str !== 'object') - BlockRenderer / BlockComposer: decode editorState data URL before passing to Lexical parseEditorState (fixes 'type undefined not found' error) - Extract decodeEditorState helper to @we/block-shared utils - Export decodeFileAsString / decodeFileAsJson from @we/models public API - Simplify fileTransforms.ts: remove dead FileData branch
1 parent b89fe31 commit deb4c5b

11 files changed

Lines changed: 59 additions & 36 deletions

File tree

packages/app-framework/src/frameworks/solid/stores/TemplateStore.tsx

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { profileTemplate, schemaTestsTemplate, settingsTemplate } from '@shared/
33
import { deepClone } from '@shared/utils';
44
import { toastService } from '@we/components/solid';
55
import type { FileData } from '@we/models';
6-
import { Template } from '@we/models';
6+
import { decodeFileAsJson, Template } from '@we/models';
77
import type { StoredTemplate, TemplateMeta, TemplateSchema } from '@we/schema-shared';
88
import { createStoredTemplate } from '@we/schema-shared';
99
import { updateSchema } from '@we/schema-solid';
@@ -123,10 +123,11 @@ export function TemplateStoreProvider(props: ParentProps) {
123123
const savedTemplates: TemplateSchema[] = [];
124124

125125
for (const template of allDbTemplates) {
126-
if (!template.schema || typeof template.schema !== 'object') continue;
126+
const decoded = decodeFileAsJson(template.schema);
127+
if (!decoded || typeof decoded !== 'object') continue;
127128

128129
// The schema field stores a StoredTemplate { schema, sections }
129-
const stored = template.schema as unknown as StoredTemplate;
130+
const stored = decoded as unknown as StoredTemplate;
130131
const schema = 'schema' in stored && stored.schema ? stored.schema : (stored as unknown as TemplateSchema);
131132
// Prefer the ID embedded in the schema (set during save) over deriving from name
132133
const templateId = schema.id || template.name?.toLowerCase().replace(/\s+/g, '-') || template.id;
@@ -388,7 +389,7 @@ export function TemplateStoreProvider(props: ParentProps) {
388389
try {
389390
const existingTemplate = savedTemplateMap.get(templateId);
390391
if (existingTemplate) {
391-
existingTemplate.schema = schemaBlob as unknown as Record<string, unknown>;
392+
existingTemplate.schema = schemaBlob as any;
392393
existingTemplate.name = name;
393394
existingTemplate.version = (existingTemplate.version || 1) + 1;
394395
await existingTemplate.save();
@@ -397,7 +398,7 @@ export function TemplateStoreProvider(props: ParentProps) {
397398
name,
398399
origin: 'custom',
399400
version: 1,
400-
schema: schemaBlob as unknown as Record<string, unknown>,
401+
schema: schemaBlob as any,
401402
});
402403
savedTemplateMap.set(templateId, newTemplate);
403404

@@ -447,7 +448,7 @@ export function TemplateStoreProvider(props: ParentProps) {
447448
try {
448449
const existingTemplate = savedTemplateMap.get(templateId);
449450
if (existingTemplate) {
450-
existingTemplate.schema = schemaBlob as unknown as Record<string, unknown>;
451+
existingTemplate.schema = schemaBlob as any;
451452
existingTemplate.name = schemaToSave.meta.name;
452453
existingTemplate.version = (existingTemplate.version || 1) + 1;
453454
await existingTemplate.save();
@@ -456,7 +457,7 @@ export function TemplateStoreProvider(props: ParentProps) {
456457
name: schemaToSave.meta.name,
457458
origin: 'custom',
458459
version: 1,
459-
schema: schemaBlob as unknown as Record<string, unknown>,
460+
schema: schemaBlob as any,
460461
});
461462
savedTemplateMap.set(templateId, newTemplate);
462463

@@ -523,7 +524,7 @@ export function TemplateStoreProvider(props: ParentProps) {
523524

524525
try {
525526
const existing = savedTemplateMap.get(templateId)!;
526-
existing.schema = schemaBlob as unknown as Record<string, unknown>;
527+
existing.schema = schemaBlob as any;
527528
existing.version = (existing.version || 1) + 1;
528529
await existing.save();
529530
} catch (error) {

packages/block-system/frameworks/solid/src/components/BlockComposer.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { ListItemNode, ListNode } from '@lexical/list';
22
import { CHECK_LIST, HEADING, ORDERED_LIST, QUOTE, UNORDERED_LIST } from '@lexical/markdown';
33
import { HeadingNode, QuoteNode } from '@lexical/rich-text';
44
import type { BlockComposerProps, SerializedBlockNode } from '@we/block-shared';
5+
import { decodeEditorState } from '@we/block-shared';
56
import { registerCoreBlocks } from '@we/block-shared';
67
import type { ColumnProps } from '@we/components/solid';
78
import { Column, Row } from '@we/components/solid';
@@ -58,8 +59,11 @@ function LoadPostIntoEditor({ post }: { post?: SerializedBlockNode }) {
5859
createEffect(() => {
5960
if (!post || !editor) return;
6061

62+
const rootNode: SerializedBlockNode = typeof post === 'string' ? decodeEditorState(post) : post;
63+
if (!rootNode) return;
64+
6165
try {
62-
const editorState = editor.parseEditorState({ root: post });
66+
const editorState = editor.parseEditorState({ root: rootNode });
6367
editor.setEditorState(editorState);
6468
} catch (error) {
6569
console.error('Error loading post data:', error);

packages/block-system/frameworks/solid/src/components/BlockRenderer.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ListItemNode, ListNode } from '@lexical/list';
22
import { HeadingNode, QuoteNode } from '@lexical/rich-text';
33
import type { BlockRendererProps, SerializedBlockNode } from '@we/block-shared';
4+
import { decodeEditorState } from '@we/block-shared';
45
import type { ColumnProps } from '@we/components/solid';
56
import { Column } from '@we/components/solid';
67
import {
@@ -21,8 +22,12 @@ function LoadPostForRenderer({ post }: { post?: SerializedBlockNode }) {
2122

2223
createEffect(() => {
2324
if (!post || !editor) return;
25+
26+
const rootNode: SerializedBlockNode = typeof post === 'string' ? decodeEditorState(post) : post;
27+
if (!rootNode) return;
28+
2429
try {
25-
const editorState = editor.parseEditorState({ root: post });
30+
const editorState = editor.parseEditorState({ root: rootNode });
2631
editor.setEditorState(editorState);
2732
} catch (error) {
2833
console.error('Error loading post data:', error);

packages/block-system/shared/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export type { BlockRegistration } from './registry';
33
export { registerBlock, getBlockRegistration, getBlockModel, updateBlockRegistration } from './registry';
44
export { registerCoreBlocks } from './core-blocks';
55
export { createBlocks, loadBlocks, blocksToLexicalJSON } from './serialization';
6+
export { decodeEditorState } from './utils';
67

78
// Back-compat: consumers importing blocks from @we/block-shared still work
89
export { CollectionBlock, ImageBlock, TextBlock } from '@we/models';

packages/block-system/shared/src/serialization.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ export async function createBlocks(
150150
data_base64: base64,
151151
name: 'editor-state.json',
152152
file_type: 'application/json',
153-
} as unknown as Record<string, unknown>;
153+
} as any;
154154
await root.save(tx.batchId);
155155
}
156156

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import type { SerializedBlockNode } from './types';
2+
3+
/**
4+
* Decode an editorState data URL (returned by resolveLanguage) into a
5+
* SerializedBlockNode (Lexical root node JSON).
6+
*
7+
* The ad4m model system resolves FILE_STORAGE_LANGUAGE CIDs into
8+
* "data:<mime>;base64,<b64>" strings. JS-function @Property transforms
9+
* are silently dropped by the Rust executor, so consumers must decode
10+
* the data URL themselves.
11+
*
12+
* Returns null if the input is not a valid data URL or the payload is
13+
* not parseable JSON.
14+
*/
15+
export function decodeEditorState(dataUrl: string | null | undefined): SerializedBlockNode | null {
16+
if (typeof dataUrl !== 'string' || !dataUrl.startsWith('data:') || !dataUrl.includes(';base64,')) return null;
17+
try {
18+
return JSON.parse(atob(dataUrl.split(';base64,')[1]));
19+
} catch {
20+
return null;
21+
}
22+
}

packages/models/src/blocks/CollectionBlock.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { Flag, HasMany, HasManyMethods, Model, Property } from '@coasys/ad4m';
22

33
import { FILE_STORAGE_LANGUAGE } from '../constants';
4-
import { decodeFileAsJson } from '../utils/fileTransforms';
54
import { WeNode } from '../WeNode';
65

76
@Model({ name: 'CollectionBlock' })
@@ -15,9 +14,8 @@ export class CollectionBlock extends WeNode {
1514
@Property({
1615
through: 'we://editor_state',
1716
resolveLanguage: FILE_STORAGE_LANGUAGE,
18-
transform: decodeFileAsJson as any,
1917
})
20-
editorState: Record<string, unknown> | null = null;
18+
editorState: string | null = null;
2119

2220
@Property({ through: 'we://type' })
2321
type: string = '';

packages/models/src/entities/Template.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { Flag, HasMany, HasManyMethods, Model, Property } from '@coasys/ad4m';
22

33
import { FILE_STORAGE_LANGUAGE } from '../constants';
4-
import { decodeFileAsJson } from '../utils/fileTransforms';
54
import { WeNode } from '../WeNode';
65
import { ChatSession } from './ChatSession';
76

@@ -22,9 +21,8 @@ export class Template extends WeNode {
2221
@Property({
2322
through: 'we://template_schema',
2423
resolveLanguage: FILE_STORAGE_LANGUAGE,
25-
transform: decodeFileAsJson as any,
2624
})
27-
schema: Record<string, unknown> = {};
25+
schema: string | null = null;
2826

2927
@HasMany(() => ChatSession, { through: 'we://chat_session' })
3028
chatSessions: ChatSession[] = [];

packages/models/src/entities/Theme.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { Flag, Model, Property } from '@coasys/ad4m';
22

33
import { FILE_STORAGE_LANGUAGE } from '../constants';
4-
import { decodeFileAsJson, decodeFileAsString } from '../utils/fileTransforms';
54
import { WeNode } from '../WeNode';
65

76
@Model({ name: 'Theme' })
@@ -25,15 +24,13 @@ export class Theme extends WeNode {
2524
@Property({
2625
through: 'we://stylesheet',
2726
resolveLanguage: FILE_STORAGE_LANGUAGE,
28-
transform: decodeFileAsString as any,
2927
})
30-
css: string = '';
28+
css: string | null = null;
3129

3230
/** Structured token overrides (primaryHue, saturation, neutralSaturation, etc.) */
3331
@Property({
3432
through: 'we://token_overrides',
3533
resolveLanguage: FILE_STORAGE_LANGUAGE,
36-
transform: decodeFileAsJson as any,
3734
})
38-
overrides: Record<string, unknown> = {};
35+
overrides: string | null = null;
3936
}

packages/models/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,4 @@ export {
3939
export type { FileData } from './utils/imageHelpers';
4040
export { normalizeSignal, denormalizeSignal } from './utils/signalNormalize';
4141
export { aggregateSignals } from './utils/signalAggregate';
42+
export { decodeFileAsString, decodeFileAsJson } from './utils/fileTransforms';

0 commit comments

Comments
 (0)