diff --git a/src/index.html b/src/index.html
index 6004971..6d081b0 100644
--- a/src/index.html
+++ b/src/index.html
@@ -4989,7 +4989,7 @@
ⓘ
-
+
@@ -9657,14 +9657,24 @@
syncInputs();
render();
}
- // Add a text-frame layer (Phase C4 structural placeholder) to
- // the master currently open in the editor. Sized to fill the
- // trim with a small inset so it reads as a "type area" out of
- // the box; the user can resize / move like any other shape.
- function addBookTextFrameToActiveMaster() {
+ // Add a text-frame layer to whichever book surface is currently
+ // active (master or designed page). Sized to fill the trim with
+ // a small inset so it reads as a "type area" out of the box;
+ // the user can resize / move like any other shape.
+ //
+ // Phase J2 generalisation: previously master-only. Designed
+ // pages can now host text frames too — each frame carries a
+ // stable `frameId` so future thread definitions (J3+) can
+ // reference it across saves and layer reorders.
+ function addBookTextFrame(targetOverride) {
const book = state.book;
- if (!book || !book.editingMasterId) return;
- const target = `book-master:${book.editingMasterId}`;
+ if (!book) return;
+ let target = targetOverride || '';
+ if (!target) {
+ if (book.editingMasterId) target = `book-master:${book.editingMasterId}`;
+ else if (book.editingEntryId) target = `book-page:${book.editingEntryId}`;
+ }
+ if (!target) return;
const s = book.spec;
const trimW = Number(s.trimW) || 6;
const trimH = Number(s.trimH) || 9;
@@ -9689,8 +9699,21 @@
shape: defaultShapeData('text-frame'),
fill: { type: 'none' },
stroke: { type: 'none', color: '#60a5fa', width: 0, dash: 'solid' },
+ // Phase J2: stable frameId for thread addressing. Independent
+ // of the layer's `id` so a thread definition survives a layer
+ // delete + re-add (if the user keeps the frameId in mind, or
+ // we surface it in the inspector).
+ frameId: makeBookEntryId(),
+ // Phase J3: when threaded, points at the next frame in the
+ // chain ({ pageId, frameId }) or null for end-of-thread.
+ nextFrame: null,
});
}
+ // Legacy alias kept so existing callers (the master-edit toolbar
+ // wiring + any saved keyboard shortcuts) keep working.
+ function addBookTextFrameToActiveMaster() {
+ return addBookTextFrame();
+ }
function setBookDefaultMaster(id) {
const book = state.book;
@@ -12778,7 +12801,10 @@
// editingPage path
if (prev) prev.style.display = '';
if (next) next.style.display = '';
- if (tfBtn) tfBtn.style.display = 'none';
+ // Phase J2: designed pages can now host text frames. The button
+ // is the same affordance as on masters — picks up the current
+ // surface from book.editingEntryId vs book.editingMasterId.
+ if (tfBtn) tfBtn.style.display = '';
if (back) back.textContent = '← Back to Pages';
const gateBtn = document.getElementById('bookPageEditGatingBtn');
if (pubBtn) {
@@ -37696,7 +37722,7 @@
});
on('bookPageEditPrev', 'click', () => gotoBookPageRelative(-1));
on('bookPageEditNext', 'click', () => gotoBookPageRelative(1));
- on('bookMasterAddTextFrame', 'click', () => addBookTextFrameToActiveMaster());
+ on('bookMasterAddTextFrame', 'click', () => addBookTextFrame());
on('bookPageEditPublish', 'click', publishBookSurface);
on('bookPageEditGatingBtn', 'click', () => {
const id = state.book?.editingEntryId;