Third revision pass adds reflowable prose as a first-class part of
the Book Designer, without giving up the fixed-layout design pages.
Architecture:
- Prose chapters live as NIP-23 long-form articles (kind-30023,
markdown body). One source, two presentations: same article event
is readable standalone on Habla.news AND laid out as designed
pages inside a book on Artstr Studio.
- Book manifest's `pages` becomes a discriminated union:
`{ type: 'design', ref }` for fixed-layout pages, and
`{ type: 'reflow', textRef, masterRef, … }` for chapter segments
that paginate dynamically.
- New `text-frame` layer kind, lives on masters. The pagination
engine pours markdown tokens into copies of the master until the
chapter is exhausted.
- New `spec.textStyles` block — a book-level stylesheet mapping
each markdown element (h1-h6 / body / blockquote / code / list /
link / hr) to typography (font, size, weight, leading, alignment,
margins, plus keepWithNext / keepTogether flow controls).
- Anchored designed pages: designed entries on the manifest use
`anchor.mode: 'before-chapter' | 'after-chapter'` so their
semantic position survives reflow-driven page-count changes.
Tradeoff mitigations baked in:
- Pagination is fiddly → tight v1 markdown subset documented;
pure-function engine for testability; "Preview pagination" before
publish; overflow toasts for too-long words / wide code blocks.
- Text drift breaks pagination → chapters pin to a specific event
id by default (safe for print); opt-in "Live" mode for
always-latest with re-paginate notices showing page-count delta;
anchor-based positioning for designed pages.
- PDF text fidelity → reflowed prose exports as real PDF text via
pdf-lib font subsetting; font allowlist with vector-path
fallback + per-export warning.
- Editor scope → plain textarea + live preview for v1; CodeMirror
upgrade deferred to Phase I; WYSIWYG never in scope.
New tokens: `{chapter-title}`, `{chapter-number}` resolve per
chapter context (useful for running headers on masters).
Phases updated:
- Phase C grows `text-frame` layer kind (structural).
- New Phase C.5 — vendor `marked`, implement pagination engine,
markdown editor pane, anchoring, pin/live tracking. Slots in
before PDF export because PDF needs it.
- Phase D1 expanded to include chapter publish as kind-30023.
- Phase E updated for real PDF text on reflowed prose.
- New Phase I — extended markdown subset (tables, footnotes,
auto-TOC via `{toc}`, cross-references) + CodeMirror editor.
Pages overview tile UX expanded: designed-page tiles show a
rendered thumbnail; reflow tiles show a stack-of-pages icon with
chapter title, master name, paginated page count, and a tiny
first-page preview.
Acceptance criteria for Phases C, C.5, D1, E updated to match.
TODO.md summary rewritten to reflect the new shape.
Apply twelve Nostr-driven revisions to the Book Designer spec:
1. **Sidecar architecture.** Pages, masters, and the cover spread
are now separate addressable kind-30078 events
(`casewrap-page`, role-discriminated). The book is a thin
`casewrap-book` manifest that references them by
`{ kind, pubkey, dTag }` — the same shape Linked Designs
already speaks. Edit one page → that page event replaces; the
manifest only changes when order / list / spec moves.
2. **Masters as Nostr objects.** Each master is its own event; one
"Body pages" master can be referenced by many books and forked
by other authors. Default master lives on the manifest spec.
3. **Cover as a sidecar event** with `role: 'cover-spread'` so it
can iterate independently and be republished as standalone
marketing art.
4. **Linked Designs is the precedent.** The resolver IIFE,
progressive fetch, localStorage disk cache, batched relay
queries, upstream-update notifications, and attribution chain
all transfer verbatim. Call this out explicitly so we don't
reinvent the wheel.
5. **Addressable refs everywhere.** Manifest entries use the
existing `{ kind, pubkey, dTag }` form (convertible to naddr at
the UI layer), not local UUIDs.
6. **Premium gating + tips.** New "Premium, tipping, gating"
section covers whole-book paywall (gate the manifest),
per-chapter paywall (gate individual page events via
PREMIUM_DESIGNS), and NIP-57 zaps at any granularity.
7. **Reader Mode** added as Phase G — the Slide Decks' Presenter
Mode counterpart, with progressive page fetch, TOC sidebar,
page-flip nav, and inline zap-to-unlock.
8. **Embed integration.** EXTERNAL_EMBED learns `?book=<naddr>`
for a flip-book iframe player; same transport + CSP rules.
9. **Per-page reactions / comments** work for free because each
page is its own addressable event (NIP-25 + kind-1). One-liner
in product goals + Reader Mode acceptance.
10. **`{page-count}` semantics pinned.** Defaults to *numbered*
pages; new `{physical-page-count}` exposes the raw total.
Plus `{book-author}` for the manifest pubkey.
11. **NIP-23 distinction.** Explicit non-goal — Book mode is
fixed-layout design, not reflowable prose; for prose use
NIP-23 long-form articles directly.
12. **Manifest schema.** Pages now carry
`{ ref, label?, inheritsMasterRef, skipNumber, gating? }`;
page-side fields that vary per-book (master, skipNumber,
label) moved off the page event onto the manifest entry.
Phase D split into D1 (publish individual page / master / cover
events) and D2 (publish the manifest); new Phase G (Reader Mode +
embed); new Phase H (premium gating). Acceptance summary,
cross-surface reactivity, and risks updated to match.
TODO.md's Book Designer summary rewritten to reflect the
sidecar plan.
New BOOK_DESIGNER_FEATURE.md drafts a top-level templateMode: 'book'
that orchestrates the existing cover designer, deck overview, and
custom-art canvas around a shared document spec (trim size, page
count, paper thickness, bleed). Spine width derives from page count
× paper thickness; the cover view goes read-only on the spine
column when in derived mode. Pages reuse the deck-sorter UI for
reordering; the per-page editor is the custom-art canvas bound to
a book-page:<id> target with prev / next page nav. Masters render
underneath each inheriting page and support {page-number} /
{page-count} / {book-title} token substitution.
PDF export ships in its own phase (E), backed by a vendored
pdf-lib (~80 KB). Single-file (cover + interior) and split (cover
PDF + interior PDF) output modes; bleed + crop-mark options;
v1 flattens text to vector for font-fidelity, with vector PDF
text as a follow-up.
Phased: A (spec + skeleton) → B (pages + editor) → C (masters) →
D (publish) → E (PDF export) → F (templates + deck-to-book
import). Non-goals: reflowable text, footnotes, automatic TOC,
CMYK / ICC, imposition, mixed page sizes in one book.
Linked from README's "Drafted-but-unbuilt" list and TODO.md.
PEN_TOOL_FEATURE.md gets a 2026-06-02 status block documenting
everything that landed on top of Phase D — Direct Selection split,
auto-add/auto-delete, click-vs-drag convert, Alt-click curved ↔
pointed, multi-anchor select / marquee / arrow nudge, drag-segment
bow, continue-endpoint, spacebar reposition, Alt mid-draw, handle
break, Cmd+J / Cmd+Alt+J, Path Actions side panel — plus the
adjacent design-tool features that shipped in the same arc
(Eyedropper, Align & distribute, Free Transform rotation,
Pathfinder, marquee box-select, dive-into-edit on dbl-click).
TODO.md gets a "Design-tool follow-ups" section listing the gaps:
Smart Guides, Gradients + shadows, Scissors / Outline text /
Command palette, Flip H / V (needs renderer scaleX/scaleY),
Pathfinder rotation support, pixel-art frame timeline.
The list had drifted from reality across the last ~30 commits.
Cleaning up so "what's next?" decisions stand on accurate ground.
Moved out of "Drafted-but-unbuilt" / polish sections and into
"Shipped (recent)":
- Native chart import phases 1–4 (bar + pie + line + polish layer).
The old "Native chart support" follow-up text under PPTX polish
was outdated — chart import is now its own arc with its own
CHART_IMPORT_FEATURE.md spec.
- PPTX Phase 7 per-run rich text spans — listed as a follow-up;
actually shipped in commit b6cf4f3.
- Premium deck publishing + multi-slide preview pager.
- Self-author auto-unlock.
- Pixel-art layer animation when embedded.
- One-click PNG / GIF download on pixel-art preview pages.
- Auto-pick category metadata via defaultCategoryForMode.
- Locked-premium UI gating (Print / Save PDF, Start Presentation,
Import-into-Disc-1/2).
- Vault durability fixes (receipt re-derive, synchronous upsert,
multi-relay union).
Fixed the stale "Branch still needs a merge into `main` once we're
done testing" line under PPTX import polish — the merge happened
ages ago.
Added a new "Chart import polish" section listing the open chart
follow-ups (stacked variants, combo charts, 3D approximations).
Added Linked Designs to "Drafted-but-unbuilt features" so the new
spec at docs/LINKED_DESIGNS_FEATURE.md surfaces for future
"what should we do next" decisions.
Phases 0–6 of the PPTX import arc shipped on pptx-import (deck
shell + slide backgrounds + text + notes + image / chart / table /
SmartArt placeholders + preset shapes with fills/strokes + group
flattening + theme color resolution + master/layout bg inheritance
+ line flip handling). Move the entry out of "Drafted-but-unbuilt
features" and add a "PPTX import polish" section listing what's
still open:
- Phase 7 per-run rich text spans (today the first run's style
applies to the whole text box; mid-paragraph styling is lost).
- Richer import report modal (per-slide warning summary, group
counts).
- Native chart support — render OOXML chart XML into Artstr layers
rather than placeholder images. Its own multi-phase arc.
- Phase 6+ inheritance gaps: gradient / picture backgrounds, font
scheme (+mj-lt → real font), placeholder geometry inheritance.
- a:custGeom → custom SVG-path conversion for arbitrary shapes
(currently fall back to rect + warning).
- Test deck library under test/pptx/ to catch future-phase
regressions automatically.
- Eventual merge of pptx-import → main.
Adds docs/PPTX_IMPORT_FEATURE.md as the canonical plan for the next
big arc: a phased editable .pptx importer that lands PowerPoint decks
as editable Artstr deck JSON.
Key decisions baked in:
- Images and unsupported objects (charts / SmartArt / tables / video)
import as placeholder image layers pointing at a hardcoded URL
(postimg.cc for now). Original PowerPoint media filenames carry
over into the layer name so the user can correlate placeholders
with their source deck.
- fflate (~10 KB) vendored as src/vendor/fflate.min.js. JSZip is too
heavy for the single-file ethos.
- Importer code lives in src/pptx-importer.js as an IIFE that
exposes window.ArtstrPptxImporter. The Import PPTX button
dynamically injects both scripts on first click, so the main
index.html bundle does not grow for the 90 % of users who never
import a PPTX.
- Notes folded into Phase 2 alongside text (single extra relationship
lookup, Presenter Mode already renders slide.notes).
- No Blossom / NIP-96 prereq, no base64 bloat, no publish-size guard.
Imported decks are normal Artstr deck payloads with remote URLs.
- Import-only for this arc. PPTX export (Artstr -> .pptx) is a
separate later arc with its own complexity.
TODO.md gets a Drafted-but-unbuilt entry pointing at the new spec.
Pre-merge docs sweep before zap-gated lands on main:
- TODO.md: move Premium out of "Drafted-but-unbuilt" into "Shipped
(recent)" with a full summary of what landed (soft-gate, watermarked
previews, NWC split-zap with 30% platform fee, purchase vault with
auto-split, profile menu, login picker, Settings, mobile polish).
Leave Phase 4 E2E + a few confirm()-modal replacements as a new
"Premium-templates polish" section.
- docs/PREMIUM_DESIGNS_FEATURE.md: flip the status banner to
"Shipped on zap-gated, merged into main 2026-05-29" and map each
spec step (0-4) to where it lives in src/.
- README.md: add a Premium encrypted designs section; mention NWC,
vault, login picker, profile dropdown, and Settings modal in the
Nostr / Lightning sections; drop "NWC zaps / zap-gated templates"
from the unbuilt list in the Files section.
In live testing, the tag-only gate shipped in Phases 0–3 turned out
to be too leaky: the design payload was published in plaintext, three
separate import paths (feed-card Use/Fork, preview-pane Use/Fork,
preview-pane Save JSON) bypassed the lock, and any non-Artstr client
could ignore the tag entirely. Adam pulled back the original detailed
spec he wrote earlier — the static soft-gate + buyer purchase vault
model — which solves the protection gap WITHOUT requiring a creator-
online bot (the route I was about to build).
Key idea: the AES key derives deterministically from
HKDF-SHA256(softGatePepperForEpoch(epoch), publicDesignSalt, info =
"artstr-softgate-v1" + epoch + creatorPubkey + premiumAddress +
schemaVersion). The pepper lives in the static client (obfuscated,
not secret). The client refuses to derive the key until valid kind-
9735 receipts confirm the split zap. Same key for every buyer; no
creator-online requirement; cross-device unlock via a NIP-44 self-
encrypted purchase vault (kind-30078, d=artstr:purchase-vault:v1).
Files:
- docs/PREMIUM_DESIGNS_FEATURE.md (new, canonical) — the full
soft-gate + split-zap + purchase-vault spec. Sections 1–17 are
the original spec verbatim. Section 18 maps the shipped Phases
0–3 to the spec's implementation-plan items (10.0–10.6) so the
next contributor sees what's done. Section 19 lists the
remaining work in build order: soft-gate crypto primitive →
watermarked preview → premium publish event → premium consume
→ receipt fast-path by address → purchase vault → My Purchases
UI → hardening + copy.
- docs/ZAP_GATED_PREMIUM_FEATURE.md — banner now points at
PREMIUM_DESIGNS_FEATURE.md as the canonical going-forward doc.
The obsolete Phases 5/6 (DM-delivery key flow) I sketched
earlier today are removed; the "Content model (CORRECTED)"
section is trimmed and points at the new spec. The file is
kept for Phases 0–3 history, the NWC/tip integration notes,
and the threat model that still applies.
- docs/ZAP_GATED_TEMPLATES_FEATURE.md and ZAPS_NWC_FEATURE.md
keep their "superseded" banners pointing at
ZAP_GATED_PREMIUM_FEATURE.md (which itself now points at
PREMIUM_DESIGNS_FEATURE.md) — chain of provenance preserved.
- TODO.md: drafted-but-unbuilt entry rewritten to describe the
final shape (encrypted + vault + honest soft-gate framing) and
point at the new spec; references the older docs for the
shipped foundation.
No code changes yet — the spec adoption is the gate. Next commits
implement the soft-gate crypto primitive, the watermarked preview,
and the premium publish path.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
The original ZAP_GATED_TEMPLATES_FEATURE.md said "no platform fee, no
backend" — that's exactly the opposite of what we actually want to
ship. ZAPS_NWC_FEATURE.md was scoped as a general tip upgrade,
listing splits as a "future enhancement." For our purposes the two
are a single arc, with NWC being the only thing that gives the
mandatory creator/platform split a tolerable UX.
ZAP_GATED_PREMIUM_FEATURE.md is the new canonical plan:
- 5 phases (0 crypto bundle, 1 NWC, 2 platform-fee plumbing,
3 zap-gated UI, 4 E2E + edge cases). ~16-23h total.
- 30% platform fee, single-constant for easy adjustment later.
- PLATFORM_NPUB hard-coded; lud16 resolved dynamically from the
npub's kind-0 each session (so the lightning address can change
later without a code update).
- CoinOS recommended via a guided "Set up with CoinOS" path in
the wallet-connection modal; generic "paste any NIP-47 URI"
fallback for Alby Hub, Mutiny, Phoenix, Cashu.me, etc.
- Idempotent retry of partial unlocks via localStorage receipt
tracking — creator-leg pays first so a half-failed unlock at
least pays the creator.
- Explicit threat model + non-goals (no creator-only fallback,
no refunds, no bolt11-QR fallback path).
The two old specs are marked superseded with a banner at the top so
historical context is preserved without future contributors getting
crossed wires. TODO.md drops them from the drafted-but-unbuilt list
and points to the merged doc; it also fixes the stale Private
Publishing entry — Phases 1 & 2 already shipped in 3e6e89e, only
Phases 3 & 4 remain.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
The pixel-art canvas-size dropdown (and the PNG-export dropdown that
shares the same class) used `position: absolute; right: 0` relative to
its toolbar wrap. The wrap lives inside `.pixelEditorBar`, which has
`flex-wrap: wrap`. When the bar wrapped and the trigger button landed
on the LEFT of its row, the menu's right-anchor pushed it 260px to the
LEFT — straight under the main sidebar.
Fix:
- Switched .pixelExportMenu to `position: fixed` (only when open).
- Added a `placeFlexMenu(menu, btn)` helper that anchors the menu's
right edge to the button's right edge by default, flips to the
button's left edge if anchoring right would push the menu past the
sidebar (using the aside's actual right edge as the safe-left
boundary, not just the viewport edge), and finally clamps to the
viewport on both sides.
- Added a fixed width (260px) + box-sizing border-box to keep the
menu from expanding to its natural content width once it's freed
from the previous absolute-positioning constraint.
- Window-resize handler auto-closes any open menu — the fixed coords
were computed against the previous layout, so resizing would
otherwise strand the menu away from its button.
Verified via CDP across 920px / 1000px / 1100px / 1440px viewports and
in both sidebar-open and sidebar-collapsed states. Also exercised the
specific failure scenario (button moved to the leftmost slot of the
bar, simulating the worst-case wrap layout) — menu now opens to the
right of the button, fully inside the canvas area.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
The undo/redo pair and the trim/sheet canvas-size pills lived inside
.topbar's flex row, which wraps at narrow widths. Adam wanted them in a
permanent, predictable place so muscle memory works regardless of
editor mode or sidebar state.
Changes:
- .status is now `position: fixed; top: var(--top-strip-height);
right: 0; flex-wrap: nowrap` with the same panel/border styling as
the Nostr widget — a second strip directly below it.
- Reordered the markup so the canvas-size pills come first and the
undo/redo group last, so undo/redo end up hugging the viewport edge
and the pills sit to their left (matching the requested layout).
- Dropped the `body.app-designer .status` override (full-width
left-aligned), since the new positioning is universal across modes.
- updateNostrWidgetSpace() now measures the wider of the Nostr widget
or the new strip and reserves topbar right padding accordingly, so
the project title can't slide under the strip.
Verified via CDP at 1440px (cover, customart, pixelart modes) and at
900px narrow — strip stays pinned top-right with undo/redo on the
outside edge in every case.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
Current JPEG flow makes the user save a PDF, then re-pick it from
disk for PDF.js to convert. We can't cache the PDF in between
because window.print() doesn't expose the generated bytes to JS.
Log the proper fix: write a direct canvas rasterizer that walks
the layer model (image / text / shape / color / qr / pixelart),
draws each layer onto an off-screen canvas at the artboard's design
dims, then toBlob('image/jpeg'). Same approach the pixel-art PNG
export already uses, generalised across all layer types. CORS-
tainted remote images fall back to the existing PDF round-trip,
with a toast explaining why. ~300–500 LoC, no new dep.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
Five items added to Adam's working list:
- Click canvas background → deselect everything in vector editor.
- Toggle crop marks for export previews (so they stay out of PDF /
JPEG output).
- Auto-size Custom Art canvas to match an imported background image's
natural dimensions (clamped to CUSTOM_ART_MAX, with the Slide
16:9-locked case skipped).
- JPEG export: hide layer-selection outlines before the raster snapshot.
- Independent-axis corner-node resize so a mostly-horizontal drag
scales width a lot and height a little — applies to all layer
resize handles, not just text.
Plus a "CORS for remote-image raster export" subsection noting that
the PDF and JPEG paths both go through window.print() / PDF.js (so
CORS doesn't actually bite today) and listing what to consider if it
ever does — Blossom / CORS-friendly hosts, an optional same-origin
proxy if we ever stand up a backend, or a direct canvas-taint
workaround.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
The Print / Save PDF button is now context-aware. In the Deck Builder
(deck mode, no slide open in the editor) it builds a hidden print-only
DOM container with one page per slide, each rendered via the same
renderCustomArtPreviewDOM the sorter thumbnails use:
- Themed at render time via composeSlideForDeck so the deck's font /
background applies (and per-slide ignoreDeckTheme is honoured).
- Each page sized to the preview's native dimensions
(PREVIEW_BASE_PX / 96 inches wide, aspect-matched from the first
slide) so the @page directive is the exact slide size and the
slide content fills the page edge-to-edge without scaling.
- `page-break-after: always` between slides; no trailing blank.
- Print-only CSS hides every other top-level element on body so
the editor chrome doesn't bleed through.
- document.title is set to "<save-stem>_deck" so the browser
defaults the saved PDF's filename helpfully.
- A `window.afterprint` listener tears the print container, the
injected stylesheet, and the title override back down — so
cancelling the dialog leaves no residue.
Inside a single-slide editor (deck mode, a slide is open), the
Print button still calls printWithCurrentFilename for the existing
single-page slide print.
TODO.md: move Slide deck PDF export and Copy / paste transform
consistency into "Shipped (recent)". Adam's working list is empty.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
Before: pasteLayersFromClipboard clamped each item's w / h individually
to the target bounds, then re-centred any item whose box landed
out-of-bounds. With multiple copied items, this:
- Collapsed every out-of-bounds item onto the same centred position
(lost their relative positions).
- Gave each item a different effective scale (lost their relative
sizes).
So a "Copy All" → cross-mode paste turned a tidy 4-layer composition
into a pile of overlapping clamped boxes.
Now: compute the bounding box once across every clipboard item, pick
a single scale (only shrinks — never enlarges — so a paste into a
larger target keeps items at their original size), and apply one
shared translation that centres the (possibly scaled) bbox in the
target. Both axes use the same scale, preserving the group's aspect
ratio. Items missing w / h fall back to 70%-of-target uniformly, so
the default-size path can't desync either.
Result:
- Two items copied with their original gap and a 2:1 size ratio
retain that gap and ratio after paste, regardless of target size.
- A wide cover → narrow disc paste shrinks everything proportionally
rather than collapsing edges onto the centre.
- Same-mode paste with everything already in-bounds is unchanged
(scale stays at 1; translation is identity).
TODO.md: move the resolved colour-picker work to "Shipped (recent)".
Co-Authored-By: Claude Opus 4.7 <[email protected]>
Two convenience buttons under the QR data textarea:
- Insert npub — async; pulls the signed-in pubkey via the Nostr
extension (window.nostr.getPublicKey), encodes it to bech32
npub, writes it into the QR data field. Toasts an error if no
extension is present.
- Insert share link — async; only valid once the design has been
published (checks state.editingDTag). Builds the same naddr /
nevent shape that shareIdForRow produces for the community-
browser Copy-share-link button:
* naddr for replaceable events (kind 30000-39999) with a
mode-prefixed d-tag (cover: / disc: / jewel: / customart: /
disc-design: / slide: / deck: / pixelart:)
* nevent fallback otherwise, using state.editingEventId
Then wraps it as `${location.origin}/#/share/<id>` and writes
it into the data field.
Both writes go through updateQrField('data', value) so undo /
history / the live preview pick them up the same way typing in
the textarea does.
TODO.md: mark QR Phase B as shipped.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
PIXEL_ART_FEATURE.md: rewrite Implementation status to reflect that
Phases A–D all shipped (embedded layer, standalone design, raster
export + editor extras, animation, image → pixelize), with the
backlog (WebP/APNG, Blossom offload, animated pixel layers driving
Stack actions) called out as deferred.
TODO.md: drop stale items now that they're resolved —
- Pixel Art (moved to a "Shipped (recent)" section pointing at the
spec doc).
- Pixel editor layout expansion (subsumed by the collapsible
sidebar — both the pixel editor and vector canvas can now use
the full viewport width).
- Print / Save event ID wrapping (the `<dd>` already uses
`overflow-wrap: anywhere; word-break: break-word`).
- TMDB auto-fill cleanup (the doc was already deleted).
The Color picker fixes item is kept but narrowed to the vector
tools — the pixel editor's in-app HSV picker shipped in Phase A.
README.md: add Pixel Art everywhere it's relevant —
- One-liner mention in the intro paragraph.
- New "Pixel art" Designer-tab layout entry alongside Slide / Disc
design.
- `pixelart` layer type added to the Layers list.
- New dedicated "Pixel art" section before Editor canvas, covering
tools / transforms / animation / export / import / undo /
animated previews.
- Editor canvas section mentions the new sidebar collapse toggle.
- Schema section lists `pixelart` template mode and layer type,
documents the `pixelArt` data-model shape, and notes that the
inline GIF89a encoder lives in `index.html` (not a vendored dep).
Co-Authored-By: Claude Opus 4.7 <[email protected]>
The on-canvas resize handles, text-box-resize behaviour, and expanded
Text tool panel all shipped in 1a517c9; collapse the section to a
shipped note and keep it open for future editor-UX items.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
On-canvas resize handles for all layer types, a constraint that text-box
resize never rescales the point size, and expanding the existing
#textToolOptions panel like the pen-tool panel.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
Spec for a Nostr-native pixel-art editor: an editable `pixelart` layer
type (palette-indexed + RLE grid stored in JSON, raster as export-only),
a dedicated Pixel Editor canvas-area view, a standalone casewrap-pixelart
Designer-tab design importable into any design as a layer, PNG export,
and a phased path to animation frames + GIF.
Includes a Size limits section: relays cap event size (commonly 64 KB),
so publishing is gated on a measured event-size budget derived from the
relays' NIP-11 max_event_size (min across the user's set, 64 KB
fallback) — soft warning at ~50%, hard block at ~90%, with a live size
readout in the editor. Encoder stores the smaller of RLE / base64 to
bound the worst case; rough size table per grid dimension included.
Also index the spec in TODO.md under drafted-but-unbuilt features.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
Spec for "HyperCard for Nostr" — fullscreen interactive card stacks built
as a focused extension of the Slide Deck: a new templateMode 'stack'
reusing the deck card-container engine, with a per-stack aspect ratio,
optional per-layer actions (goto-card / open-url), transparent hotspots,
and a fullscreen interactive viewer with click routing. Declarative
safe schema only — no HTML/JS. Phased A-D plus a /p/<naddr> viewer route.
Also index both PRIVATE_PUBLISHING_FEATURE.md and STACKS_FEATURE.md under
the drafted-but-unbuilt features in TODO.md.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
Build a Nostr-native presentation system on top of the existing canvas
editor:
- Slide (templateMode 'slide') — a 16:9 Custom Art canvas with speaker
notes; publishes/forks/loads as casewrap-slide.
- Deck Builder (templateMode 'deck') — a Template-tab container with an
inline ordered slide list. Slide-sorter view: add blank, import from
Nostr or file (non-16:9 imports letterboxed), duplicate, delete,
drag-reorder, inline-rename; edit a slide in the canvas editor with
write-back. Self-contained save/load/publish/fork as casewrap-deck;
deck feed-card and publish previews show the first slide + count badge.
- Deck theme — deck-level font + background, render-time composition,
per-slide ignoreDeckTheme opt-out.
- Presenter Mode — full-screen runtime: themed slide, notes, next
thumbnail, counter; keyboard/click/on-screen nav; Presenter ⇄ Audience
view toggle (slide-only full-screen with auto-hiding chrome). Launches
from the deck tool palette and a deck's community-browser preview.
Supporting changes:
- New categories: slide ("Slide design") and slide-deck ("Slide deck");
both use the free-form Custom tag field, not media identifiers.
- Swap the Template/Designer tab icons; 50% larger.
- Remove the IMDb/OMDb metadata import (button, handler, omdbApiKey);
the imdbId field stays.
- Update README, SLIDE_DECK_FEATURE.md status, and TODO.md.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
- New left-palette tools: Hand (pan), Shape (drag to draw a primitive),
Text (click to place). Removed the redundant sidebar add-buttons.
- Pencil strokes now keep a constant on-screen thickness (pixel-space
viewBox) instead of scaling with stroke length/aspect.
- Restructured tabs: "Disc Designer" -> "Designer", which now hosts a
Layout panel with Custom Art (default) + Disc Design; Custom Art moved
off the Template tab. Underlying appMode/save-format unchanged.
- Renamed the app from CaseWrap Studio to Artstr Studio (brand text only;
Nostr tags and localStorage keys kept for compatibility); smaller
one-line logo wordmark.
- README updated for the new tabs, tools, and keyboard shortcuts.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
QR code layer (Phase A):
- New `qr` layer type. Add QR button alongside Image / Text / Color /
Shape in every layer panel. Spawns 1:1 with a placeholder URL.
- Controls: data textarea, error-correction level (L/M/Q/H), quiet
zone, module + background colors, transparent-background toggle.
- Renders as crisp SVG on the editor and all five preview surfaces
(dark modules merged into one path); canvas/PDF/JPEG export draws
it cell-by-cell. Round-trips through JSON and Nostr publish/fork;
old clients skip the unknown type. Works with drag / resize /
rotate / opacity / aspect-lock / clip / clipboard / undo-redo.
Combined-wrap layer panel:
- In cover mode with Artwork mode "One combined wrap image", the
layer panel collapses the separate Back / Spine / Front panels
into a single "Cover trim area" panel — the wrap is one open
canvas, so the sub-regions aren't meaningful. The combined panel
still lists any pre-existing back/spine/front layers via a
matchKeys set so they aren't orphaned from the UI.
Profile modal close fix:
- closeProfilePage used history.back(), which navigated the browser
out of the app entirely when the profile was cold-loaded from a
shared /u/<npub> link (that entry being first in the tab's
history). Switched to history.replaceState to "/" — cleans the
URL in place without navigating or firing popstate. Matches how
closeNostrPreview already handles the /share/ URL.
Docs:
- Add TODO.md tracking deferred work: QR Phase B/C, IMDb-import
removal, profile-page polish, edit-flow polish, and the unbuilt
badges / collections / zap-gated / NWC-zaps features.
- Remove docs/UNDO_REDO_FEATURE.md and docs/QR_CODE_LAYER_FEATURE.md
(shipped) and docs/TMDB_AUTOFILL_FEATURE.md (declined).
- README: document QR layers, undo/redo, fit-to-view + zoom toolbar,
and the off-canvas veil; refresh the schema and docs references.
Co-Authored-By: Claude Opus 4.7 <[email protected]>