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;