@pantheon-systems/puck-css 0.4.2 → 0.4.4
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/dist/core/config.d.ts +3 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +5 -6
- package/dist/core/config.js.map +1 -1
- package/dist/core/featureConfig.d.ts +2 -0
- package/dist/core/featureConfig.d.ts.map +1 -1
- package/dist/core/featureConfig.js +4 -0
- package/dist/core/featureConfig.js.map +1 -1
- package/dist/core/types.d.ts +58 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/data/dal/index.d.ts +5 -1
- package/dist/data/dal/index.d.ts.map +1 -1
- package/dist/data/dal/index.js +22 -1
- package/dist/data/dal/index.js.map +1 -1
- package/dist/data/dal/init.d.ts.map +1 -1
- package/dist/data/dal/init.js +22 -14
- package/dist/data/dal/init.js.map +1 -1
- package/dist/data/dal/p1-store.d.ts +15 -1
- package/dist/data/dal/p1-store.d.ts.map +1 -1
- package/dist/data/dal/p1-store.js +51 -5
- package/dist/data/dal/p1-store.js.map +1 -1
- package/dist/data/dal/types.d.ts +13 -1
- package/dist/data/dal/types.d.ts.map +1 -1
- package/dist/data/page-store.d.ts +17 -2
- package/dist/data/page-store.d.ts.map +1 -1
- package/dist/data/page-store.js +47 -5
- package/dist/data/page-store.js.map +1 -1
- package/dist/data/route-templates.js +1 -1
- package/dist/data/route-templates.js.map +1 -1
- package/dist/editor/P1App.d.ts.map +1 -1
- package/dist/editor/P1App.js +1 -1
- package/dist/editor/P1App.js.map +1 -1
- package/dist/editor/P1PuckProvider.d.ts.map +1 -1
- package/dist/editor/P1PuckProvider.js +175 -66
- package/dist/editor/P1PuckProvider.js.map +1 -1
- package/dist/editor/plugin/P1Plugin.d.ts +16 -1
- package/dist/editor/plugin/P1Plugin.d.ts.map +1 -1
- package/dist/editor/plugin/P1Plugin.js +35 -4
- package/dist/editor/plugin/P1Plugin.js.map +1 -1
- package/dist/editor/plugin/createP1Overrides.d.ts.map +1 -1
- package/dist/editor/plugin/createP1Overrides.js +4 -1
- package/dist/editor/plugin/createP1Overrides.js.map +1 -1
- package/dist/editor/useDocuments.d.ts +4 -1
- package/dist/editor/useDocuments.d.ts.map +1 -1
- package/dist/editor/useDocuments.js +5 -2
- package/dist/editor/useDocuments.js.map +1 -1
- package/dist/editor/useP1Editor.d.ts +12 -0
- package/dist/editor/useP1Editor.d.ts.map +1 -1
- package/dist/editor/useP1Editor.js +40 -5
- package/dist/editor/useP1Editor.js.map +1 -1
- package/dist/editor/useP1Plugin.d.ts +1 -1
- package/dist/editor/useP1Plugin.d.ts.map +1 -1
- package/dist/editor/useP1Plugin.js +2 -0
- package/dist/editor/useP1Plugin.js.map +1 -1
- package/dist/editor/useRealtime.d.ts +9 -4
- package/dist/editor/useRealtime.d.ts.map +1 -1
- package/dist/editor/useRealtime.js +11 -6
- package/dist/editor/useRealtime.js.map +1 -1
- package/dist/features/content-type-templates/editor/useTemplateEditor.d.ts +20 -0
- package/dist/features/content-type-templates/editor/useTemplateEditor.d.ts.map +1 -0
- package/dist/features/content-type-templates/editor/useTemplateEditor.js +62 -0
- package/dist/features/content-type-templates/editor/useTemplateEditor.js.map +1 -0
- package/dist/features/content-type-templates/editor/useTemplatePermissions.d.ts +7 -0
- package/dist/features/content-type-templates/editor/useTemplatePermissions.d.ts.map +1 -0
- package/dist/features/content-type-templates/editor/useTemplatePermissions.js +12 -0
- package/dist/features/content-type-templates/editor/useTemplatePermissions.js.map +1 -0
- package/dist/features/content-type-templates/editor/useTemplateScaffold.d.ts +19 -0
- package/dist/features/content-type-templates/editor/useTemplateScaffold.d.ts.map +1 -0
- package/dist/features/content-type-templates/editor/useTemplateScaffold.js +37 -0
- package/dist/features/content-type-templates/editor/useTemplateScaffold.js.map +1 -0
- package/dist/features/content-type-templates/hooks/useTemplateList.d.ts +41 -0
- package/dist/features/content-type-templates/hooks/useTemplateList.d.ts.map +1 -0
- package/dist/features/content-type-templates/hooks/useTemplateList.js +72 -0
- package/dist/features/content-type-templates/hooks/useTemplateList.js.map +1 -0
- package/dist/features/content-type-templates/index.d.ts +18 -0
- package/dist/features/content-type-templates/index.d.ts.map +1 -0
- package/dist/features/content-type-templates/index.js +13 -0
- package/dist/features/content-type-templates/index.js.map +1 -0
- package/dist/features/content-type-templates/permissions/createPuckPermissions.d.ts +68 -0
- package/dist/features/content-type-templates/permissions/createPuckPermissions.d.ts.map +1 -0
- package/dist/features/content-type-templates/permissions/createPuckPermissions.js +90 -0
- package/dist/features/content-type-templates/permissions/createPuckPermissions.js.map +1 -0
- package/dist/features/content-type-templates/permissions/role-permissions.d.ts +49 -0
- package/dist/features/content-type-templates/permissions/role-permissions.d.ts.map +1 -0
- package/dist/features/content-type-templates/permissions/role-permissions.js +79 -0
- package/dist/features/content-type-templates/permissions/role-permissions.js.map +1 -0
- package/dist/features/content-type-templates/permissions/useContentRole.d.ts +37 -0
- package/dist/features/content-type-templates/permissions/useContentRole.d.ts.map +1 -0
- package/dist/features/content-type-templates/permissions/useContentRole.js +34 -0
- package/dist/features/content-type-templates/permissions/useContentRole.js.map +1 -0
- package/dist/features/content-type-templates/permissions/useResolveContentRole.d.ts +38 -0
- package/dist/features/content-type-templates/permissions/useResolveContentRole.d.ts.map +1 -0
- package/dist/features/content-type-templates/permissions/useResolveContentRole.js +83 -0
- package/dist/features/content-type-templates/permissions/useResolveContentRole.js.map +1 -0
- package/dist/features/content-type-templates/stores/index.d.ts +9 -0
- package/dist/features/content-type-templates/stores/index.d.ts.map +1 -0
- package/dist/features/content-type-templates/stores/index.js +8 -0
- package/dist/features/content-type-templates/stores/index.js.map +1 -0
- package/dist/features/content-type-templates/stores/template-store-api.d.ts +17 -0
- package/dist/features/content-type-templates/stores/template-store-api.d.ts.map +1 -0
- package/dist/features/content-type-templates/stores/template-store-api.js +56 -0
- package/dist/features/content-type-templates/stores/template-store-api.js.map +1 -0
- package/dist/features/content-type-templates/stores/template-store.d.ts +54 -0
- package/dist/features/content-type-templates/stores/template-store.d.ts.map +1 -0
- package/dist/features/content-type-templates/stores/template-store.js +80 -0
- package/dist/features/content-type-templates/stores/template-store.js.map +1 -0
- package/dist/features/content-type-templates/types.d.ts +114 -0
- package/dist/features/content-type-templates/types.d.ts.map +1 -0
- package/dist/features/content-type-templates/types.js +7 -0
- package/dist/features/content-type-templates/types.js.map +1 -0
- package/dist/features/content-type-templates/ui/ActionBarPinButton.d.ts +3 -0
- package/dist/features/content-type-templates/ui/ActionBarPinButton.d.ts.map +1 -0
- package/dist/features/content-type-templates/ui/ActionBarPinButton.js +77 -0
- package/dist/features/content-type-templates/ui/ActionBarPinButton.js.map +1 -0
- package/dist/features/content-type-templates/ui/TemplateManagerOverlay.d.ts +18 -0
- package/dist/features/content-type-templates/ui/TemplateManagerOverlay.d.ts.map +1 -0
- package/dist/features/content-type-templates/ui/TemplateManagerOverlay.js +167 -0
- package/dist/features/content-type-templates/ui/TemplateManagerOverlay.js.map +1 -0
- package/dist/features/content-type-templates/ui/TemplatePinPanel.d.ts +18 -0
- package/dist/features/content-type-templates/ui/TemplatePinPanel.d.ts.map +1 -0
- package/dist/features/content-type-templates/ui/TemplatePinPanel.js +11 -0
- package/dist/features/content-type-templates/ui/TemplatePinPanel.js.map +1 -0
- package/dist/features/content-type-templates/ui/TemplateSelector.d.ts +41 -0
- package/dist/features/content-type-templates/ui/TemplateSelector.d.ts.map +1 -0
- package/dist/features/content-type-templates/ui/TemplateSelector.js +32 -0
- package/dist/features/content-type-templates/ui/TemplateSelector.js.map +1 -0
- package/dist/features/content-type-templates/ui/dataToTemplate.d.ts +25 -0
- package/dist/features/content-type-templates/ui/dataToTemplate.d.ts.map +1 -0
- package/dist/features/content-type-templates/ui/dataToTemplate.js +37 -0
- package/dist/features/content-type-templates/ui/dataToTemplate.js.map +1 -0
- package/dist/features/content-type-templates/validation/structural-validation.d.ts +29 -0
- package/dist/features/content-type-templates/validation/structural-validation.d.ts.map +1 -0
- package/dist/features/content-type-templates/validation/structural-validation.js +69 -0
- package/dist/features/content-type-templates/validation/structural-validation.js.map +1 -0
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/p1/editor/connect/connect-field-modal.d.ts.map +1 -1
- package/dist/p1/editor/connect/connect-field-modal.js +23 -1
- package/dist/p1/editor/connect/connect-field-modal.js.map +1 -1
- package/dist/p1/editor/connect/field-connect-plugin.d.ts.map +1 -1
- package/dist/p1/editor/connect/field-connect-plugin.js +2 -6
- package/dist/p1/editor/connect/field-connect-plugin.js.map +1 -1
- package/dist/p1/pages/conflict-resolution-panel.d.ts +17 -0
- package/dist/p1/pages/conflict-resolution-panel.d.ts.map +1 -0
- package/dist/p1/pages/conflict-resolution-panel.js +74 -0
- package/dist/p1/pages/conflict-resolution-panel.js.map +1 -0
- package/dist/p1/pages/content-type-template-list.d.ts +7 -0
- package/dist/p1/pages/content-type-template-list.d.ts.map +1 -0
- package/dist/p1/pages/content-type-template-list.js +108 -0
- package/dist/p1/pages/content-type-template-list.js.map +1 -0
- package/dist/p1/pages/create-content-type-form.d.ts +7 -0
- package/dist/p1/pages/create-content-type-form.d.ts.map +1 -0
- package/dist/p1/pages/create-content-type-form.js +102 -0
- package/dist/p1/pages/create-content-type-form.js.map +1 -0
- package/dist/p1/pages/create-page-form.d.ts +8 -1
- package/dist/p1/pages/create-page-form.d.ts.map +1 -1
- package/dist/p1/pages/create-page-form.js +67 -8
- package/dist/p1/pages/create-page-form.js.map +1 -1
- package/dist/p1/pages/create-template-form.d.ts +6 -1
- package/dist/p1/pages/create-template-form.d.ts.map +1 -1
- package/dist/p1/pages/create-template-form.js +61 -15
- package/dist/p1/pages/create-template-form.js.map +1 -1
- package/dist/p1/pages/hooks.d.ts +6 -1
- package/dist/p1/pages/hooks.d.ts.map +1 -1
- package/dist/p1/pages/hooks.js +13 -2
- package/dist/p1/pages/hooks.js.map +1 -1
- package/dist/p1/pages/index.d.ts +5 -0
- package/dist/p1/pages/index.d.ts.map +1 -1
- package/dist/p1/pages/index.js +5 -0
- package/dist/p1/pages/index.js.map +1 -1
- package/dist/p1/pages/migration-conflict-queue.d.ts +8 -0
- package/dist/p1/pages/migration-conflict-queue.d.ts.map +1 -0
- package/dist/p1/pages/migration-conflict-queue.js +62 -0
- package/dist/p1/pages/migration-conflict-queue.js.map +1 -0
- package/dist/p1/pages/migration-preview-panel.d.ts +12 -0
- package/dist/p1/pages/migration-preview-panel.d.ts.map +1 -0
- package/dist/p1/pages/migration-preview-panel.js +157 -0
- package/dist/p1/pages/migration-preview-panel.js.map +1 -0
- package/dist/p1/pages/migration-status-panel.d.ts +7 -0
- package/dist/p1/pages/migration-status-panel.d.ts.map +1 -0
- package/dist/p1/pages/migration-status-panel.js +198 -0
- package/dist/p1/pages/migration-status-panel.js.map +1 -0
- package/dist/p1/pages/structure-page.d.ts.map +1 -1
- package/dist/p1/pages/structure-page.js +75 -18
- package/dist/p1/pages/structure-page.js.map +1 -1
- package/dist/p1/pages/structure-tabs.d.ts +11 -0
- package/dist/p1/pages/structure-tabs.d.ts.map +1 -0
- package/dist/p1/pages/structure-tabs.js +45 -0
- package/dist/p1/pages/structure-tabs.js.map +1 -0
- package/dist/pds/components/P1EditorHeader.d.ts +6 -2
- package/dist/pds/components/P1EditorHeader.d.ts.map +1 -1
- package/dist/pds/components/P1EditorHeader.js +6 -3
- package/dist/pds/components/P1EditorHeader.js.map +1 -1
- package/dist/pds/components/PageNavigator.d.ts +7 -2
- package/dist/pds/components/PageNavigator.d.ts.map +1 -1
- package/dist/pds/components/PageNavigator.js +28 -7
- package/dist/pds/components/PageNavigator.js.map +1 -1
- package/dist/pds/components/PageNavigator.module.css +70 -0
- package/package.json +3 -3
- package/src/styles.css +331 -0
|
@@ -15,6 +15,8 @@ import { useDocuments } from './useDocuments.js';
|
|
|
15
15
|
import { resolveFeatureConfig } from '../core/featureConfig.js';
|
|
16
16
|
import { resolveActivePlugins, composeProviders } from './composePlugins.js';
|
|
17
17
|
import { DEFAULT_CSS_FEATURE_PLUGINS } from './defaultPlugins.js';
|
|
18
|
+
import { createPuckPermissions } from '../features/content-type-templates/permissions/createPuckPermissions.js';
|
|
19
|
+
import { useTemplateList } from '../features/content-type-templates/hooks/useTemplateList.js';
|
|
18
20
|
/**
|
|
19
21
|
* Provider component for P1 Puck integration.
|
|
20
22
|
*
|
|
@@ -63,7 +65,10 @@ agentModeEnabled = false, agentId, agentTrigger: _agentTrigger,
|
|
|
63
65
|
// Phase 9: Callbacks
|
|
64
66
|
onPresenceChange, onAgentConflict: _onAgentConflict,
|
|
65
67
|
// Plugin system props (B.4)
|
|
66
|
-
featurePlugins, featureConfig,
|
|
68
|
+
featurePlugins, featureConfig,
|
|
69
|
+
// Content Type Templates (PROPOSAL-010)
|
|
70
|
+
// Consumers should resolve the role via useResolveContentRole and pass it here.
|
|
71
|
+
userRole = 'editor', children, }) {
|
|
67
72
|
// Access notification context
|
|
68
73
|
const notificationContext = useNotifications();
|
|
69
74
|
// Persist selected branch in sessionStorage so it survives provider remounts
|
|
@@ -120,6 +125,8 @@ featurePlugins, featureConfig, children, }) {
|
|
|
120
125
|
// Document state
|
|
121
126
|
const [currentDocument, setCurrentDocument] = useState(null);
|
|
122
127
|
const [currentData, setCurrentData] = useState(null);
|
|
128
|
+
// Template state (PROPOSAL-010)
|
|
129
|
+
const [currentTemplate, setCurrentTemplate] = useState(null);
|
|
123
130
|
// Save state
|
|
124
131
|
const [saveStatus, setSaveStatus] = useState('idle');
|
|
125
132
|
const [lastSaved, setLastSaved] = useState(null);
|
|
@@ -129,9 +136,10 @@ featurePlugins, featureConfig, children, }) {
|
|
|
129
136
|
const currentDocumentRef = useRef(null);
|
|
130
137
|
const initializedRef = useRef(false);
|
|
131
138
|
// Track the latest Puck action metadata for inclusion in sync payloads.
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
|
|
139
|
+
// Buffered actions captured by handleAction (Puck's onAction callback)
|
|
140
|
+
// Array accumulates all structural actions during an edit session.
|
|
141
|
+
// Forwarded to backend on save for version history, then cleared.
|
|
142
|
+
const pendingActionsRef = useRef([]);
|
|
135
143
|
// Tracks the document path that the current data in state belongs to.
|
|
136
144
|
// Set alongside every setCurrentData call to record the data's origin.
|
|
137
145
|
// Checked before every write (realtime or REST) to prevent cross-document
|
|
@@ -208,6 +216,9 @@ featurePlugins, featureConfig, children, }) {
|
|
|
208
216
|
actorType: 'user',
|
|
209
217
|
enabled: enableRealtime && !!wsBaseUrl,
|
|
210
218
|
initialData: currentData,
|
|
219
|
+
onServerReload: () => {
|
|
220
|
+
notificationContext.addInfo('Document is being refreshed by the server. Reconnecting...');
|
|
221
|
+
},
|
|
211
222
|
// WebSocket presence callbacks - receive instant presence updates
|
|
212
223
|
onPresenceUpdate: (actors) => {
|
|
213
224
|
// Mark WebSocket presence as active (first update received)
|
|
@@ -222,13 +233,7 @@ featurePlugins, featureConfig, children, }) {
|
|
|
222
233
|
setWsPresenceActors((prev) => prev.map((a) => (a.actorId === actorId ? { ...a, focusRegions } : a)));
|
|
223
234
|
},
|
|
224
235
|
onRemoteUpdate: (data) => {
|
|
225
|
-
const componentCount = data.content?.length ?? 0;
|
|
226
|
-
const zoneCount = data.zones ? Object.keys(data.zones).length : 0;
|
|
227
|
-
console.log('[P1PuckProvider] onRemoteUpdate received:', `components=${componentCount}, zones=${zoneCount},`, `pendingRemoteUpdates=${pendingRemoteUpdatesRef.current},`, `viewingVersion=${viewingVersionRef.current !== null}`);
|
|
228
|
-
// Don't apply remote updates while viewing a historical version
|
|
229
|
-
// The user is viewing read-only historical data and shouldn't see live changes
|
|
230
236
|
if (viewingVersionRef.current !== null) {
|
|
231
|
-
console.log('[P1PuckProvider] onRemoteUpdate SKIPPED: viewing historical version');
|
|
232
237
|
return;
|
|
233
238
|
}
|
|
234
239
|
// Reject empty data that would overwrite real editor content.
|
|
@@ -239,7 +244,6 @@ featurePlugins, featureConfig, children, }) {
|
|
|
239
244
|
if (data.content.length === 0 &&
|
|
240
245
|
(!rootProps || Object.keys(rootProps).length === 0) &&
|
|
241
246
|
!data.zones) {
|
|
242
|
-
console.log('[P1PuckProvider] onRemoteUpdate SKIPPED: empty data rejected');
|
|
243
247
|
return;
|
|
244
248
|
}
|
|
245
249
|
// Store the latest data - will be used when debounce fires
|
|
@@ -260,8 +264,6 @@ featurePlugins, featureConfig, children, }) {
|
|
|
260
264
|
}
|
|
261
265
|
const dataToSync = pendingRemoteDataRef.current;
|
|
262
266
|
if (dataToSync) {
|
|
263
|
-
const syncComponentCount = dataToSync.content?.length ?? 0;
|
|
264
|
-
console.log('[P1PuckProvider] onRemoteUpdate APPLYING:', `components=${syncComponentCount},`, `pendingRemoteUpdates will be=${pendingRemoteUpdatesRef.current + 1}`);
|
|
265
267
|
// Increment counter to skip the onChange echo(es) that will fire
|
|
266
268
|
// when Puck processes the setCurrentData call below.
|
|
267
269
|
// The content-based guards in puckDataToYMap and RealtimeClient
|
|
@@ -342,7 +344,6 @@ featurePlugins, featureConfig, children, }) {
|
|
|
342
344
|
const dataOriginPath = currentDataDocumentPathRef.current;
|
|
343
345
|
if (dataOriginPath !== currentPath)
|
|
344
346
|
return;
|
|
345
|
-
console.log('[P1PuckProvider] PuckDataCapture catch-up: sending missed data,', `components=${currentData.content?.length ?? 0}`);
|
|
346
347
|
realtimeRef.current.applyLocalChange(currentData);
|
|
347
348
|
lastSentDataRef.current = dataJson;
|
|
348
349
|
}, 800);
|
|
@@ -369,17 +370,31 @@ featurePlugins, featureConfig, children, }) {
|
|
|
369
370
|
const userClient = useMemo(() => client.withPrincipal({ id: userId, type: 'user' }), [client, userId]);
|
|
370
371
|
// Document list for current branch
|
|
371
372
|
const { documents: branchDocuments, loading: documentsLoading, create: createDocumentRaw, remove: removeDocumentRaw, } = useDocuments({ client: userClient, siteId, branchId });
|
|
373
|
+
// Template list for current branch
|
|
374
|
+
const { templates: branchTemplates, loading: templatesLoading, error: templatesError, refresh: refreshTemplates, } = useTemplateList(userClient, siteId, branchId);
|
|
372
375
|
// Stable document create/delete callbacks
|
|
373
376
|
const branchIdRef = useRef(branchId);
|
|
374
377
|
branchIdRef.current = branchId;
|
|
375
378
|
const createDocumentRawRef = useRef(createDocumentRaw);
|
|
376
379
|
createDocumentRawRef.current = createDocumentRaw;
|
|
377
|
-
const stableCreateDocument = useCallback(async (path) => {
|
|
380
|
+
const stableCreateDocument = useCallback(async (path, template) => {
|
|
378
381
|
if (!branchIdRef.current) {
|
|
379
382
|
throw new Error('Cannot create document: no branch selected');
|
|
380
383
|
}
|
|
381
|
-
|
|
382
|
-
|
|
384
|
+
if (template) {
|
|
385
|
+
// Fetch the full template — the list endpoint may omit the components array
|
|
386
|
+
const fullTemplate = await userClient.templates.get(siteId, branchIdRef.current, template.id);
|
|
387
|
+
const { scaffoldFromTemplate } = await import('../features/content-type-templates/editor/useTemplateScaffold.js');
|
|
388
|
+
const initialData = scaffoldFromTemplate(fullTemplate);
|
|
389
|
+
await createDocumentRawRef.current(path, initialData, {
|
|
390
|
+
templateId: fullTemplate.id,
|
|
391
|
+
templateVersion: fullTemplate.version,
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
await createDocumentRawRef.current(path);
|
|
396
|
+
}
|
|
397
|
+
}, [userClient, siteId]);
|
|
383
398
|
const removeDocumentRawRef = useRef(removeDocumentRaw);
|
|
384
399
|
removeDocumentRawRef.current = removeDocumentRaw;
|
|
385
400
|
const stableDeleteDocument = useCallback(async (documentId, _path) => {
|
|
@@ -394,33 +409,29 @@ featurePlugins, featureConfig, children, }) {
|
|
|
394
409
|
setBranchesLoading(true);
|
|
395
410
|
const branchList = await userClient.branches.list(siteId);
|
|
396
411
|
setBranches(branchList);
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
if (
|
|
402
|
-
|
|
403
|
-
if (persisted && branchList.some((b) => b.id === persisted)) {
|
|
404
|
-
effectiveBranchId = persisted;
|
|
405
|
-
}
|
|
412
|
+
let effectiveBranchId = branchIdRef.current;
|
|
413
|
+
// If no branchId set, try persisted branch, then default to main
|
|
414
|
+
if (!effectiveBranchId) {
|
|
415
|
+
const persisted = getPersistedBranchId();
|
|
416
|
+
if (persisted && branchList.some((b) => b.id === persisted)) {
|
|
417
|
+
effectiveBranchId = persisted;
|
|
406
418
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
419
|
+
}
|
|
420
|
+
// Validate that the branch exists in the list; fall back to main if not
|
|
421
|
+
if (effectiveBranchId && !branchList.some((b) => b.id === effectiveBranchId)) {
|
|
422
|
+
const mainBranch = branchList.find((b) => b.isMain);
|
|
423
|
+
effectiveBranchId = mainBranch?.id ?? effectiveBranchId;
|
|
424
|
+
}
|
|
425
|
+
// Default to main if still empty
|
|
426
|
+
if (!effectiveBranchId) {
|
|
427
|
+
const mainBranch = branchList.find((b) => b.isMain);
|
|
428
|
+
if (mainBranch) {
|
|
429
|
+
effectiveBranchId = mainBranch.id;
|
|
418
430
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
});
|
|
431
|
+
}
|
|
432
|
+
persistBranchId(effectiveBranchId);
|
|
433
|
+
setBranchId(effectiveBranchId);
|
|
434
|
+
setCurrentBranch(branchList.find((b) => b.id === effectiveBranchId) ?? null);
|
|
424
435
|
}
|
|
425
436
|
catch (error) {
|
|
426
437
|
console.error('Failed to load branches:', error);
|
|
@@ -468,11 +479,16 @@ featurePlugins, featureConfig, children, }) {
|
|
|
468
479
|
setSaveError(null);
|
|
469
480
|
try {
|
|
470
481
|
await withRetry(async () => {
|
|
482
|
+
const actionsToSend = pendingActionsRef.current.length > 0
|
|
483
|
+
? [...pendingActionsRef.current]
|
|
484
|
+
: undefined;
|
|
471
485
|
await userClient.versions.create(siteId, {
|
|
472
486
|
documentId: doc.id,
|
|
473
487
|
branchId,
|
|
474
488
|
snapshot: dataToSave,
|
|
489
|
+
puckActions: actionsToSend,
|
|
475
490
|
});
|
|
491
|
+
pendingActionsRef.current = [];
|
|
476
492
|
}, { maxAttempts: maxRetries });
|
|
477
493
|
// Note: We intentionally do NOT call setCurrentData(dataToSave) here.
|
|
478
494
|
// The data is already in Puck's internal state (it came from Puck's onChange).
|
|
@@ -517,30 +533,34 @@ featurePlugins, featureConfig, children, }) {
|
|
|
517
533
|
// This captures the action type and relevant metadata fields so they
|
|
518
534
|
// can be included in the sync payload for backend version storage.
|
|
519
535
|
const handleAction = useCallback((action) => {
|
|
520
|
-
const
|
|
536
|
+
const puckAction = {
|
|
537
|
+
type: action.type || 'unknown',
|
|
538
|
+
};
|
|
521
539
|
if (action.componentType)
|
|
522
|
-
|
|
540
|
+
puckAction.componentType = action.componentType;
|
|
523
541
|
if (action.componentId)
|
|
524
|
-
|
|
542
|
+
puckAction.componentId = action.componentId;
|
|
525
543
|
if (action.zone)
|
|
526
|
-
|
|
544
|
+
puckAction.zone = action.zone;
|
|
527
545
|
if (action.sourceIndex !== undefined)
|
|
528
|
-
|
|
546
|
+
puckAction.sourceIndex = action.sourceIndex;
|
|
529
547
|
if (action.destinationIndex !== undefined)
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
548
|
+
puckAction.destinationIndex = action.destinationIndex;
|
|
549
|
+
if (action.sourceZone)
|
|
550
|
+
puckAction.sourceZone = action.sourceZone;
|
|
551
|
+
if (action.destinationZone)
|
|
552
|
+
puckAction.destinationZone = action.destinationZone;
|
|
553
|
+
const buffer = pendingActionsRef.current;
|
|
554
|
+
buffer.push(puckAction);
|
|
555
|
+
if (buffer.length > 1000) {
|
|
556
|
+
pendingActionsRef.current = buffer.slice(-500);
|
|
557
|
+
}
|
|
535
558
|
}, []);
|
|
536
559
|
// Public save function (triggers debounce)
|
|
537
560
|
// Also resumes auto-save if paused, per user requirement
|
|
538
561
|
// Sends changes via WebSocket when realtime is enabled (but not for remote updates)
|
|
539
562
|
const saveData = useCallback((data) => {
|
|
540
|
-
const componentCount = data.content?.length ?? 0;
|
|
541
|
-
// Suppress the onChange echo from PuckDataSynchronizer after loadDocument.
|
|
542
563
|
if (suppressNextSaveRef.current) {
|
|
543
|
-
console.log(`[P1PuckProvider] saveData SKIPPED: suppressNextSave (components=${componentCount})`);
|
|
544
564
|
suppressNextSaveRef.current = false;
|
|
545
565
|
return;
|
|
546
566
|
}
|
|
@@ -551,14 +571,10 @@ featurePlugins, featureConfig, children, }) {
|
|
|
551
571
|
// getHasUnsavedChanges() will incorrectly report unsaved changes.
|
|
552
572
|
if (enableRealtime && realtime.connected) {
|
|
553
573
|
if (pendingRemoteUpdatesRef.current > 0) {
|
|
554
|
-
// Counter indicates this onChange is from a remote update or data load
|
|
555
|
-
console.log(`[P1PuckProvider] saveData SKIPPED: pendingRemoteUpdates=${pendingRemoteUpdatesRef.current} (components=${componentCount})`);
|
|
556
574
|
pendingRemoteUpdatesRef.current -= 1;
|
|
557
575
|
return;
|
|
558
576
|
}
|
|
559
577
|
else if (viewingVersionRef.current !== null) {
|
|
560
|
-
// User is viewing historical version - don't broadcast or save
|
|
561
|
-
console.log(`[P1PuckProvider] saveData SKIPPED: viewing historical version (components=${componentCount})`);
|
|
562
578
|
return;
|
|
563
579
|
}
|
|
564
580
|
else {
|
|
@@ -569,24 +585,29 @@ featurePlugins, featureConfig, children, }) {
|
|
|
569
585
|
// into state after currentDocument has been updated to X.
|
|
570
586
|
const dataOriginPath = currentDataDocumentPathRef.current;
|
|
571
587
|
if (dataOriginPath !== currentPath) {
|
|
572
|
-
console.warn('[P1PuckProvider] saveData SKIPPED: data origin mismatch.', 'dataOrigin:', dataOriginPath, 'currentDoc:', currentPath
|
|
588
|
+
console.warn('[P1PuckProvider] saveData SKIPPED: data origin mismatch.', 'dataOrigin:', dataOriginPath, 'currentDoc:', currentPath);
|
|
573
589
|
return;
|
|
574
590
|
}
|
|
575
591
|
// Connection identity guard: verify the realtime connection is
|
|
576
592
|
// bound to the same document we're currently editing.
|
|
577
593
|
const connectedPath = realtime.connectedDocumentPath;
|
|
578
594
|
if (currentPath !== connectedPath) {
|
|
579
|
-
console.warn('[P1PuckProvider] saveData SKIPPED: connection identity mismatch.', 'currentDoc:', currentPath, 'connectedDoc:', connectedPath
|
|
595
|
+
console.warn('[P1PuckProvider] saveData SKIPPED: connection identity mismatch.', 'currentDoc:', currentPath, 'connectedDoc:', connectedPath);
|
|
580
596
|
return;
|
|
581
597
|
}
|
|
582
598
|
// Local user edit — send via WebSocket (DO handles persistence)
|
|
583
599
|
// Echo prevention is handled at lower layers:
|
|
584
600
|
// - puckDataToYMap no-ops when Y.Doc already has identical data
|
|
585
601
|
// - RealtimeClient.lastSentSnapshot drops sends matching last sent/received
|
|
586
|
-
|
|
587
|
-
|
|
602
|
+
const actions = pendingActionsRef.current;
|
|
603
|
+
if (actions.length > 0) {
|
|
604
|
+
realtime.applyLocalChange(data, actions);
|
|
605
|
+
}
|
|
606
|
+
else {
|
|
607
|
+
realtime.applyLocalChange(data);
|
|
608
|
+
}
|
|
588
609
|
trackSentData(data);
|
|
589
|
-
|
|
610
|
+
pendingActionsRef.current = [];
|
|
590
611
|
// Data sent via WebSocket — DO handles persistence.
|
|
591
612
|
// Update save status directly (skip debouncedSave/performSave chain).
|
|
592
613
|
setSaveStatus('saved');
|
|
@@ -594,8 +615,6 @@ featurePlugins, featureConfig, children, }) {
|
|
|
594
615
|
return;
|
|
595
616
|
}
|
|
596
617
|
}
|
|
597
|
-
// Non-realtime path: mark data as pending and trigger debounced REST save
|
|
598
|
-
console.log(`[P1PuckProvider] saveData via REST (debounced): components=${componentCount}, realtimeEnabled=${enableRealtime}, connected=${realtime.connected}`);
|
|
599
618
|
pendingDataRef.current = data;
|
|
600
619
|
if (debouncedSave.isPaused()) {
|
|
601
620
|
debouncedSave.resume();
|
|
@@ -658,6 +677,23 @@ featurePlugins, featureConfig, children, }) {
|
|
|
658
677
|
currentDataDocumentPathRef.current = null;
|
|
659
678
|
setCurrentData(null);
|
|
660
679
|
setCurrentDocument(doc);
|
|
680
|
+
// Fetch template if document is bound to one (PROPOSAL-010)
|
|
681
|
+
if (doc.templateId) {
|
|
682
|
+
try {
|
|
683
|
+
if (!userClient.templates) {
|
|
684
|
+
throw new Error('Templates endpoint not available on client');
|
|
685
|
+
}
|
|
686
|
+
const template = await userClient.templates.get(siteId, branchId, doc.templateId);
|
|
687
|
+
setCurrentTemplate(template);
|
|
688
|
+
}
|
|
689
|
+
catch (templateError) {
|
|
690
|
+
console.warn('[P1PuckProvider] Failed to fetch template:', templateError);
|
|
691
|
+
setCurrentTemplate(null);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
else {
|
|
695
|
+
setCurrentTemplate(null);
|
|
696
|
+
}
|
|
661
697
|
// Get latest version
|
|
662
698
|
const version = await userClient.versions.getLatest(siteId, branchId, doc.id);
|
|
663
699
|
const puckData = version.snapshot;
|
|
@@ -805,6 +841,56 @@ featurePlugins, featureConfig, children, }) {
|
|
|
805
841
|
// Computed property for whether viewing historical version
|
|
806
842
|
const isViewingHistoricalVersion = viewingVersion !== null;
|
|
807
843
|
// =========================================================================
|
|
844
|
+
// Content Type Templates: Permission Resolution (PROPOSAL-010)
|
|
845
|
+
// =========================================================================
|
|
846
|
+
// Resolve template: either from document binding or registry path
|
|
847
|
+
const resolvedTemplate = useMemo(() => {
|
|
848
|
+
if (currentTemplate)
|
|
849
|
+
return currentTemplate;
|
|
850
|
+
const path = currentDocument?.path;
|
|
851
|
+
if (!path)
|
|
852
|
+
return null;
|
|
853
|
+
const match = path.match(/^_registry\/templates\/(.+)$/);
|
|
854
|
+
if (!match)
|
|
855
|
+
return null;
|
|
856
|
+
return branchTemplates.find((t) => t.name === match[1]) ?? null;
|
|
857
|
+
}, [currentTemplate, currentDocument?.path, branchTemplates]);
|
|
858
|
+
// Create Puck permissions resolver based on current template and user role
|
|
859
|
+
const resolvePermissions = useMemo(() => createPuckPermissions(resolvedTemplate, userRole, isViewingHistoricalVersion), [resolvedTemplate, userRole, isViewingHistoricalVersion]);
|
|
860
|
+
// Show notification when template list fails to load
|
|
861
|
+
useEffect(() => {
|
|
862
|
+
if (templatesError && showErrorNotifications && notificationContext?.addError) {
|
|
863
|
+
notificationContext.addError(`Failed to load templates: ${templatesError.message}`, () => void refreshTemplates());
|
|
864
|
+
}
|
|
865
|
+
}, [templatesError]);
|
|
866
|
+
// =========================================================================
|
|
867
|
+
// Content Type Templates: Re-fetch template on tab focus (CUJ-15a)
|
|
868
|
+
// =========================================================================
|
|
869
|
+
useEffect(() => {
|
|
870
|
+
if (typeof document === 'undefined')
|
|
871
|
+
return;
|
|
872
|
+
const handler = async () => {
|
|
873
|
+
if (document.visibilityState !== 'visible')
|
|
874
|
+
return;
|
|
875
|
+
const doc = currentDocumentRef.current;
|
|
876
|
+
if (!doc?.templateId || !userClient.templates)
|
|
877
|
+
return;
|
|
878
|
+
try {
|
|
879
|
+
const freshTemplate = await userClient.templates.get(siteId, branchId, doc.templateId);
|
|
880
|
+
setCurrentTemplate((prev) => {
|
|
881
|
+
if (prev && prev.version === freshTemplate.version)
|
|
882
|
+
return prev;
|
|
883
|
+
return freshTemplate;
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
catch {
|
|
887
|
+
// Silently ignore — stale template is better than no template
|
|
888
|
+
}
|
|
889
|
+
};
|
|
890
|
+
document.addEventListener('visibilitychange', handler);
|
|
891
|
+
return () => document.removeEventListener('visibilitychange', handler);
|
|
892
|
+
}, [siteId, userClient]);
|
|
893
|
+
// =========================================================================
|
|
808
894
|
// Phase 9: Presence & Agent Mode State
|
|
809
895
|
// =========================================================================
|
|
810
896
|
// Presence state (when presenceEnabled)
|
|
@@ -1254,6 +1340,10 @@ featurePlugins, featureConfig, children, }) {
|
|
|
1254
1340
|
const switchBranchRef = useRef(switchBranch);
|
|
1255
1341
|
switchBranchRef.current = switchBranch;
|
|
1256
1342
|
const stableSwitchBranch = useCallback((newBranchId) => switchBranchRef.current(newBranchId), []);
|
|
1343
|
+
// Get pending actions for forwarding to backend
|
|
1344
|
+
const getPendingActions = useCallback(() => {
|
|
1345
|
+
return [...pendingActionsRef.current]; // Return copy to prevent external mutation
|
|
1346
|
+
}, []);
|
|
1257
1347
|
const loadDocumentRef = useRef(loadDocument);
|
|
1258
1348
|
loadDocumentRef.current = loadDocument;
|
|
1259
1349
|
const stableLoadDocument = useCallback((path) => loadDocumentRef.current(path), []);
|
|
@@ -1388,6 +1478,8 @@ featurePlugins, featureConfig, children, }) {
|
|
|
1388
1478
|
sendFocusRegions: realtime.sendFocusRegions,
|
|
1389
1479
|
// Puck action metadata capture - pass as onAction to <Puck>
|
|
1390
1480
|
handleAction,
|
|
1481
|
+
// Get pending actions for backend forwarding (PROPOSAL-010)
|
|
1482
|
+
getPendingActions,
|
|
1391
1483
|
// Phase 9: Presence & Agent values
|
|
1392
1484
|
// Use getter to avoid context recreation on every focus-region update.
|
|
1393
1485
|
// Expose humanPresenceCount/hasActiveHumans/hasActiveAgents directly so every
|
|
@@ -1406,6 +1498,14 @@ featurePlugins, featureConfig, children, }) {
|
|
|
1406
1498
|
// Internal: realtime data capture for catch-up (sends missed keystrokes)
|
|
1407
1499
|
_realtimeDataCaptureRef: enableRealtime ? realtimeDataCaptureRef : null,
|
|
1408
1500
|
_onRealtimeDataCapture: enableRealtime ? handleRealtimeDataCapture : null,
|
|
1501
|
+
// Content Type Templates (PROPOSAL-010)
|
|
1502
|
+
userRole,
|
|
1503
|
+
templates: branchTemplates,
|
|
1504
|
+
templatesLoading,
|
|
1505
|
+
templatesError,
|
|
1506
|
+
refreshTemplates,
|
|
1507
|
+
currentTemplate,
|
|
1508
|
+
resolvePermissions,
|
|
1409
1509
|
}), [
|
|
1410
1510
|
userClient,
|
|
1411
1511
|
notificationContext,
|
|
@@ -1451,6 +1551,7 @@ featurePlugins, featureConfig, children, }) {
|
|
|
1451
1551
|
remoteSyncKey,
|
|
1452
1552
|
realtime.sendFocusRegions,
|
|
1453
1553
|
handleAction,
|
|
1554
|
+
getPendingActions,
|
|
1454
1555
|
handleRealtimeDataCapture,
|
|
1455
1556
|
// Phase 9 dependencies (full presenceState excluded — accessed via getter/ref,
|
|
1456
1557
|
// but humanPresenceCount/hasActiveHumans/hasActiveAgents are direct values so every
|
|
@@ -1465,6 +1566,14 @@ featurePlugins, featureConfig, children, }) {
|
|
|
1465
1566
|
dismissConflict,
|
|
1466
1567
|
enableRealtime,
|
|
1467
1568
|
resolvedFeatureConfig,
|
|
1569
|
+
// Content Type Templates
|
|
1570
|
+
userRole,
|
|
1571
|
+
branchTemplates,
|
|
1572
|
+
templatesLoading,
|
|
1573
|
+
templatesError,
|
|
1574
|
+
refreshTemplates,
|
|
1575
|
+
currentTemplate,
|
|
1576
|
+
resolvePermissions,
|
|
1468
1577
|
]);
|
|
1469
1578
|
// Wrap with PresenceContext.Provider when presence is enabled
|
|
1470
1579
|
const wrappedChildren = presenceContextValue ? (_jsx(PresenceContext.Provider, { value: presenceContextValue, children: children })) : (children);
|