CMS authoring: partial editor, Blocks, content-region layouts, theme-fork + Modules taxonomy - #103
Merged
Merged
Conversation
- /get-tiger: shipped marketing page (IndexController::getTigerAction + view + routes.ini alias) — the four ways to run Tiger, vibe-stack comparison, portability, in the /vibe family. - Bengal skin: warm near-black ground + burnt tiger-orange, big bold headings; registered in the skin-switcher catalog. - default.css: named content-width classes (.container-narrow/-menu/-wide/ -full-bleed) for all skins; .container-menu matches the navbar width. CMS visual builder (design.phtml + tiger.page-builder.js): - Load the real front-end stylesheets into the GrapesJS canvas (was unstyled). - Canvas light/dark toggle; body background now tracks the theme token. - Non-editable, locked header/footer chrome preview (Elementor-style), stripped from the saved body. - Partials/composition primitive: [partial name="x"] shortcode (recursive, cycle/depth-guarded) + a GrapesJS Partial widget (live preview, exports the shortcode); designAction feeds the named partials. - Fix: Menu/Partial widgets are hover-selectable (pointer-events moved off the component root onto an inner preview wrapper). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A GrapesJS project can carry lone surrogates / invalid UTF-8 (JS strings) that
make a strict json_encode return false — an empty string that fails the meta
column's json_valid CHECK (SQLSTATE 23000 / err 4025). Encode with
JSON_INVALID_UTF8_SUBSTITUTE (then '{}' as a last resort) so saving a builder
page never writes invalid JSON.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The real cause of the saveDesign 4025 error: the injected header/footer chrome preview deeply nests the GrapesJS project blob, and MariaDB's json_valid (the page.meta CHECK) rejects JSON nested >= 32 levels — PHP encodes it fine, MariaDB refuses it. (The earlier UTF-8 hardening was defensive, not the cause.) The chrome is now removed from ALL saved data (body/css/project) on save and re-injected for continued editing; suppressUpdate keeps that churn from marking the doc dirty. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…dity Migration 0041: page.meta, page_version.meta, media.variants, media.scan_meta JSON -> LONGTEXT, plus a sweep that drops every json_valid CHECK in the schema. MariaDB's JSON type carries an implicit CHECK(json_valid) that rejects JSON nested >= 32 levels (PHP encodes it fine) — it broke the CMS builder storing its deep GrapesJS project in page.meta. LONGTEXT holds any depth; the app already json_encode/decodes these values. One-way (no down to JSON). AGENTS.md documents the convention: new tables use LONGTEXT, never the JSON type. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The GrapesJS canvas iframe painted light (data-bs-theme default) before the theme was applied on load, flashing to dark. Init tbMode from the builder's own data-bs-theme (not 'light') so the first frame:load applies the right mode, and hide the .gjs-frame until JS marks it ready (theme applied + chrome placed, via rAF), then fade it in — with a 2.5s safety reveal so it can't stay blank. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A layout is now 'just a partial': a visually-built layout body places the page inside itself via [content], and composes header/footer from [partial]. The renderer already passes the page body to a layout as $context['content']; the phtml path used <?= $content ?>, this gives html/builder layouts the same slot. Empty (harmless) outside a layout render. Completes the composition primitive. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Partial editor — a partial now edits INSIDE its layout's chrome (the page builder inverted: layout = locked context, partial = the one editable region). Reuses the whole locked-chrome / canvas-CSS / light-dark stack. - Association via the reused layout_key column (no migration) + a [Layout ▾] picker that persists the choice; default = the theme header/footer. - Auto slot-detection: a layout that NAMES the partial ([partial name=key]) edits it in its own slot (header/footer); any other partial edits in the [content] area (hero/CTA). _splitLayoutForPartial cuts the rendered layout on a marker into locked chrome-before / chrome-after. - Save writes back only the partial body (chrome stripped). Blocks — the copy-in twin of the reference-placed Partial widget. - New type=block fragment primitive (model/form/list/datatable filter). - 'My Blocks' builder palette: dropping a Block inlines its editable HTML into the page (a detached copy), reusing the theme-components insert path. - Save as Block: persist the selected element as a reusable Block + live-add it to the palette (Cms_Service_Page::saveBlock, create-from-selection). - Block masters are editable in the builder (fragment chrome, 'Block' badge). Partials and Blocks are reachable from the content list (Design button now covers page/partial/block; Type filter gains Block). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The activation gate strips a deactivated module from the dispatch map (so its controllers/services are unreachable), and the nav/usermenu/header/fields/ schedule registries all skip inactive modules — but Tiger_Acl_Acl::_aclIniPaths globbed EVERY module dir, so an inactive module's acl.ini still registered its roles/resources/rules into the ACL graph. Gate it on Tiger_Model_Module:: inactiveSlugs(), mirroring Tiger_Admin_Nav; fail-safe (DB not ready -> load all). Real leak: the inactive 'apidocs' module ships an acl.ini that was loading. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AUTHORING.md — the page-building workflow of record: every start is a page-store row, seeded from blank / a core BS5 starter / a theme; human + agent share the /api door; the shell-vs-body-skeleton seam. Theme fork generalized beyond pages — Tiger_Theme gains layouts()/partials()/ template($kind,$slug)/forkables() (fingerprint-cached) so a theme's tiger:layout and tiger:partial files fork into editable type=layout/partial rows; Cms_Service_Page::forkTheme takes a kind + bakes the origin theme's stylesheet links into a forked page's head (self-loads its CSS via the theme symlink). Only the ACTIVE theme surfaces (Tiger_Theme::active) — an installed-but-inactive theme has no asset symlink, so its content can't render and must not be forkable; forkTheme refuses a non-active theme. The 'Theme Templates' tab is now a server-side DataTable (Cms_Service_Page:: themeTemplates) with a Theme column, since a theme may ship hundreds (Porto ~830); 'customized' is matched by origin (theme|kind|slug) in the fork's meta. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A module's taxonomy (type + category) now travels with it and is retained after install, so the installed list agrees with what Add Module showed. - Discovery reads type + category from module.json; an untyped routed module defaults to 'plugin' (the WordPress model), theme->theme, snippets->code. - Install captures the source taxonomy: module.type/category columns (migration 0042); Tiger_Module_Installer records the listing/manifest values; the Modules admin resolves stored -> live manifest -> default. - The 17 first-party modules ship a module.json declaring their taxonomy (cms/blog app:content, agent app:ai, media plugin:media, code/schedule developer:tools, …) — self-describing, matching the registry vocabulary. - The Modules screen is now a client-side DataTable (sort/page/search) with a Type column and a taxonomy type filter (All | Apps | Themes | Plugins | Developer …), its interactions rewired as delegated handlers to survive redraws. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A 'layout' is now a content-region template that renders INSIDE the theme shell's <main>, not a whole page — so a CMS user composing a layout never touches the shell's plumbing (SEO/analytics/consent/assets/scripts), and a layout is structurally incapable of carrying injection code. - PageController::viewAction retires the self-contained 'layout_key owns the whole document' path: every CMS page renders through the theme shell, and its layout composes the content region within it (verified end-to-end: single document, no nested <html>). Safe — 0 live pages used the old behavior. Dropped two now-dead helpers (_injectBefore/_consentBanner). - Header/footer stay chrome the SHELL renders (they need the theme view scope — themeAssets, nav helper, auth placeholders — a CMS render can't supply); their content stays editable via the partial editor, just not re-placed per layout. - PUMA ships the starter set as forkable tiger:layout files: Full Width, Sidebar Left, Sidebar Right, Two Sidebars (+ a Sidebar partial) — asides/columns via [content] + [partial] slots, zero plumbing. - AUTHORING.md §1 updated to nail the shell-vs-content-region split. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…avior - Tiger_Theme::active() derived its key from the boot-time THEME constant, which ignores a runtime Tiger_ThemeDir override (an admin preview, or a test's temp theme) — so forkTheme recorded the wrong source_key. Derive it from the manifest (else the dir basename minus a theme- prefix) instead. (Caught by the forkTheme integration test.) - DiscoveryTest: a routed module's default type is now 'plugin', not 'module'. - CmsControllerTest: the Theme Templates tab is a DataTable now — assert themeCount, not the removed themeName / inline template list. - CoreControllerActionsTest: the self-contained-layout path is retired — rewrite to assert the content-region model (body wrapped in its layout, rendered through the shell; head_html/body_scripts flow to the shell slots). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…view test The [content] shortcode isn't registered in the reduced integration bootstrap, so the html layout left it literal. phtml pulls the body via the renderer context var, independent of shortcode registration (matches the prior test's approach). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Feature release: in-context partial editor + Blocks, content-region layouts + PUMA starter set, theme-fork workflow + Theme-Templates DataTable, Modules taxonomy retention + Type-column DataTable, and the acl.ini activation-gate fix. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
CMS authoring overhaul, plus a marketing page, a skin, and two platform fixes. 11 commits, ~50 files.
Highlights
CMS builder + composition
layout_keyassociation + a[Layout ▾]picker, auto slot-detection (a partial the layout names edits in its own slot; any other edits in the[content]area), save writes back only the partial.type=blockfragment, a My Blocks palette (drop = an editable copy), and Save as Block (create-from-selection, live palette add).layoutis now a template that renders inside the theme shell's<main>(not a self-contained document), so a CMS user never touches the shell plumbing. PUMA ships Full Width · Sidebar Left · Sidebar Right · Two Sidebars (+ a Sidebar partial) as forkabletiger:layoutfiles.saveDesignmeta-encode fixes, strip locked chrome from the saved project.Theme → CMS fork workflow
Tiger_Themegainslayouts()/partials()/template()/forkables()(fingerprint-cached);forkThemeforks a theme'stiger:layout/tiger:partialfiles into editable rows, gated to the active theme. The Theme Templates tab is a server-side DataTable (a theme may ship 800+).AUTHORING.md— the page-building workflow of record (shell vs content-region seam; human + agent share the/apidoor).Modules screen
type/categoryafter install (Discovery reads the manifest; install persists it — migration0042; resolves stored → manifest → default; routed default is nowplugin). The 17 first-party modules ship amodule.jsondeclaring their taxonomy.Fixes
acl.iniactivation-gate — an inactive module'sacl.inino longer loads (it was leaking, e.g.apidocs).0041) — DB stores text; the app owns JSON validity (MariaDB'sjson_validrejects depth ≥ 32, which broke the builder's deep project blob).Prod / theme
Migrations
0041_json_to_longtext,0042_module_taxonomy— both additive.Verification
Every change was exercised on
tiger-dev(a real vendored install): builder flows, theme forks, the multi-column layouts end-to-end (single document, no nested<html>), the module-taxonomy distribution, and theacl.inigate.🤖 Generated with Claude Code