11 KiB
ARCHITECTURE MAP (Claude-oriented, dense)
Maintained by Claude for Claude. Goal: reconstruct the mental model of
/srcfast. Keep terse. Rule: update this file in the SAME commit as any structural change (file added/removed/renamed/moved, or a responsibility shift). A Stop hook (.claude/hooks/check-architecture-note.sh) reminds Claude Code sessions. No line counts here — they drift; describe structure & responsibility only. Legend:→calls/depends-on ·⊙Pinia store ·✎mutates customCss ·⚠flag/gotcha ·☠dead code (no live importer) Status: composables/stores/utils mapped. components/ = partial (only flags + tree); fill on demand.
DATA FLOW (the whole app in 8 lines)
- Kirby (PHP templates) renders narrative HTML into a hidden
#content-sourcediv + injects the Vue SPA. App.vueboots: loads narrative (⊙narrative) + stylesheet (⊙stylesheet), mounts double-buffered preview iframes.usePreviewRendererbuilds each iframesrcdoc= fontFaceCss + base css + customCss + PagedJS polyfill +#content-sourceinnerHTML.- User hovers/clicks inside iframe →
useIframeInteractions(listeners attached on iframe load) routes target → the right popup. - Popups (Element/Image/Carte) + Settings panels edit CSS via ⊙stylesheet write API →
customCsschanges. watch(stylesheet.content)inusePreviewRendererre-renders the hidden frame + crossfades. Scroll % preserved.- Settings↔Popup share page-level defaults via ⊙textDefaults/⊙imageDefaults/⊙carteDefaults (read when a toggle is OFF).
- Save: ⊙stylesheet.saveCustomCss() / ⊙narrative content save → Kirby REST (CSRF via utils/kirby-auth).
Central hub = ⊙stylesheet (everything CSS funnels through it). Selection/visual state lives in useIframeInteractions.
stores/ (all shared global state; Pinia setup-stores)
stylesheet.js⊙ — THE CSS hub. State: baseCss, customCss, fontFaceCss, isEditing, isDirty/isSaving, narrativeId.content=computed(fontFace+base+custom). Read API: extractBlock(sel) [custom→base], extractCustomBlock, extractValue(sel,prop,includeCommented=true)[numeric|string|commented], extractRawValue(sel,prop)[string], extractNumericValue(sel,prop,units), extractSpacing(sel,prop,units)[{simple}|{detailed}]. Write API ✎: updateProperty(sel,prop,val,unit), updateStyle(sel,prop,preformattedVal), removeProperty(sel,prop), replaceBlock(old,new), replaceInCustomCss(s,r), setCustomCss, comment/uncommentCssBlock, getBlockState. createRule = internal helper of updateStyle. Also: applyDefaultsCss() seeds defaults from utils/defaults; loadStylesheet/loadBaseCss; initializeFromNarrative; saveCustomCss→Kirby. ⚠ extractValue vs extractRawValue: DIFFERENT semantics, same-ish name. updateProperty(val,unit separate) vs updateStyle(pre-formatted) — both exist.narrative.js⊙ — narrative data (content tree + nav), content-visibility dirty state, save content to Kirby.textDefaults.js/imageDefaults.js/carteDefaults.js⊙ — page-level defaults bridge. Each: refs (textDefaults: fontFamily/fontSize/lineHeight/color; image|carte: width/height) +_initialized. Settings write, Popup reads when toggle OFF. (Were singleton composables → converted to Pinia.)
utils/ (pure, no reactivity unless noted)
defaults.js— frozen constants: PAGE/TEXT/ELEMENT/HEADING/PARAGRAPH_CLASS/IMAGE/CARTE/INLINE_DEFAULTS. Single source of default values.css-parsing.js— extractCssBlock, extractCssValue, updateCssValue, parseValueWithUnit (default export obj) + namedparseCssProperties(block)→{prop:val}.css-comments.js— comment/uncomment/extract commented CSS blocks (inheritance toggling). Used by ⊙stylesheet.unit-conversion.js— convertUnit(v,from,to) for px/mm/rem/em.element-settings-descriptors.js— SETTING_GROUPS, SPECIAL_GROUPS(['font','fontSize','lineHeight','color']), INDEPENDENT_TAGS(set), createStyleProps(refs), createUnitProps(refs). Feeds the element settings engine.debounce.js— createDebouncer(delay)→{debouncedUpdate(cb)}.popup-position.js— createPopupPositioner(w,h)→{calculatePosition(event)} (viewport-clamped).coloris.js— initColoris(overrides) — color-picker (@melloware/coloris) global init/config.kirby-auth.js— getCsrfToken().
composables/ (real Vue composables only)
Element settings (3-file unit, refactored — orchestrator/engine/model)
useElementSettings.js— ORCHESTRATOR for ElementPopup. Owns selector/currentTag refs, per-selector persistence (elementStatesMap), toggle cache, open/close lifecycle (open→initTogglesAndCache/applyTagDefaults/applyStoredValues), all watchers.guard={active,deactivateNextTick()}suppresses watcher feedback during self-writes. Exposes flat API consumed by ElementPopup. → useElementStyleState, useElementCss, ⊙stylesheet, ⊙textDefaults, useProjectFonts, createDebouncer.useElementCss.js✎ — ENGINE: the only store↔refs↔preview boundary. writer(updateProp/removeProps/applyGroup/removeSpecialGroupProps), display computeds(displayedCss/editableFullCss/elementCss + hasInCss/isInline/isIndependent), sync(syncRefsFromCss→applyParsedGroupToRefs/enableGroupFromParsed/resetSpecialGroupToInherited, handleCssInput→writeCssBlock), loader(loadValuesFromStylesheet). helpers: parseLength/cssString.useElementStyleState.js— reactive MODEL: one ref per CSS prop + settingEnabled + settingCache + styleProps/unitProps (from descriptors). No logic.
Preview / iframe / interaction
useIframeInteractions.js— hover labels + click routing inside iframe → element/image/carte popups. getSelectorFromElement, getContentElement, getImageFigure, getCarteFigure, handleIframeMouseMove/Click, popup-close handlers. (Page-template dead code removed → archive/page-template-styling/.)usePreviewRenderer.js— double-buffered iframe render w/ crossfade + scroll-% persistence. Builds srcdoc. watches ⊙stylesheet.content & ⊙narrative.data → renderPreview. setKeyboardShortcutHandler. Forwards Space key from iframe to parent for pan mode. → Coloris.close on iframe click.usePreviewControls.js— zoom (scale + --preview-h CSS var) + horizontal pan (Space+drag, overlay fixe, ±50% limit) + auto-centering. Wheel zoom proportional (deltaY×0.001). Replaces former ZoomControls logic and @panzoom/panzoom (removed).usePrintPreview.js— collect iframe styles, swap into document, wait for images, inject print background CSS (computed values from iframe :root), window.print(), reload.useKeyboardShortcuts.js— Cmd/Ctrl+S(save)/+P(print), Esc(close popup),\(toggle panel). Listens on document + attachToIframe.
Fonts / spacing
useProjectFonts.js— SINGLETON. PROJECT_FONTS list, @font-face gen, loadFont(name)→⊙stylesheet.setFontFaceCss, loadAllFontPreviews (select preview), loadFontsFromCss. loadedFontsCss computed.useSpacingEditor.js— margin/padding reactive (4 sides) + lock + unit convert. Used by ElementPopup (margin/padding passed into useElementSettings).usePageSettings.js— all @page state + CSS read/write for PageSettings.vue (format/dimensions, margins, background, page numbers/running title via updatePageFooters CSS surgery). PageSettings.vue is now a thin view (Coloris wiring only).
components/ (TODO: full map on demand)
Tree: App.vue → PagedJsWrapper(content-source), EditorPanel→{PageSettings,TextSettings,FigureDefaultsSettings×2(kind=image|carte),StylesheetViewer,ContentPanel→ContentTreeItem}, ElementPopup + FigureSettingsPopup×2 (kind=image|carte) (+BasePopup), PreviewLoader, SaveButton, PrintButton, ZoomControls. ui/: BasePopup, NumberInput, InputWithUnit, UnitToggle, ZoomControls, CssFileImport. blocks/: per content-block render (Text/Heading/Image/Video/Audio/Map/Quote/List) via blocks/index.js. Popups expose imperatively (defineExpose: open/close/visible/handleClick); App wires refs into useIframeInteractions.
ui/SettingSection.vue— reusable toggleable section (checkbox toggle + label header, body slot, optional #footer + #header-extra slots). Used by ElementPopup & FigureSettingsPopup. emits toggle/section-click. (Settings panels use raw non-toggle setting__section markup.) ⚠ Popup close re-entry: BasePopup.close() emits 'close', wired back to the host's handleClose → its close logic. Host close MUST guardif (basePopup.value?.visible) basePopup.value.close()or it infinite-recurses.FigureSettingsPopup.vue(thin view) +useFigureSettings.js(composable) — per-figure width/height/margin/position editor for block-image & block-carte. propkind('image'|'carte') selects baseClass + selector derivation + defaults store. Composable logic in named helpers: read{Width,Height,Margins,Position}FromCss (store→data), apply*, loadFromCss/restoreFromStored, parse*FromText/persistStateAsCss (manual edits). exposes handleClick(figure,event). (Merged from former ImagePopup/CartePopup.)editor/FigureDefaultsSettings.vue— default width/height panel for images & cartes. propkind; image targets [.block-image,.geoformat-cover-image], carte targets [.block-carte]; writes ⊙image/carteDefaults. (Merged from former ImageSettings/CarteSettings.)
PHP templates (public/site/templates/) — notable additions
form.php— Page avec deux formulaires de contact (autonome / partenaire). POST handling natif Kirby : CSRF (csrf()), honeypot,kirby()->email(), Post/Redirect/Get (go()). Blueprint :blueprints/pages/form.yml(writertext+email_totext). CSS :assets/css/form.css(chargé conditionnellement dansheader-web.php). Pas de plugin tiers.
PHP snippets (public/site/snippets/)
cover-img.php— snippet réutilisable pour les images de couverture responsives.blocks/gallery.php— rendu du bloc galerie : grille CSS avec--gallery-cols(1–4 colonnes), srcset 2 tailles, liensclass="glightbox". Blueprint :blueprints/blocks/gallery.yml. Preview panel : composant natif Kirby (preview: gallerydans le blueprint — ne pas créer de plugink-gallery-block-previewcar Kirby en fournit un). Génère un<img>avec 3 breakpointswsrcset, crop Kirby (ratio configurable via$imgW/$imgH, défaut 900×600 = 3:2), point focus panel → position nommée (crop) +object-positionCSS. Variables :$file,$alt,$lazy(bool, défaut true),$sizes(string),$imgW,$imgH. Utilisé par home.php, projects.php, actus.php, actu.php.
REFACTOR BACKLOG
- (low priority)
StylesheetViewer.vue&ContentTreeItem.vueare long but mostly<style>CSS with short, simple scripts — not worth splitting.
ARCHIVED (removed from live tree, restorable)
archive/page-template-styling/— "page template styling" feature (PagePopup + its wiring). See its REINSTALL.md. Removed orphans (no importer): useLinkedSpacing.js, ui/MarginEditor.vue, SidePanel.vue (git history only).