particle-academy/fancy-cms
A Laravel CMS package from Particle Academy offering content management features for Fancy-based projects. Early-stage with minimal adoption and limited documentation, so review the source and test thoroughly before using it in production.
Architecture fit: This is not a typical Laravel package (no service provider, facades, Eloquent, queues, or config publishing implied by the release notes). It is a pure-PHP page-document engine consisting of: an op/reducer "spine" (PageOp variants, Reducer::reduce/reduceAll), a validation layer (PageDoc/PageOp with structured path errors), a JSON codec (PageJson), a CSS/style token system (theme tokens, spacing, grids, sticky, visibility), and a renderer (PageRenderer). That is a deterministic document model + renderer, closer to a headless CMS core than a web framework package. For a TPM this is good news: it is trivially embeddable in any Laravel app as a library, with no framework coupling to fight. The risk is the inverse — it does not give you storage, persistence, auth, tenancy, HTTP endpoints, or admin UX. Those are yours to build (or to pair with fancy-cms-ui 0.8, which the release explicitly targets).
Integration feasibility: High for the read/render path; moderate for the write path. The reducer is pure and the wire format has documented inverses, so you can persist ops as an append-only log, or persist the PageDoc snapshot and apply ops transactionally. Validation is enforced at the storage boundary via PageDoc::apply (validate-before, validate-after, reject semantic no-ops), which is exactly the guardrail a TPM wants before trusting user-generated content in a multi-tenant DB. The PageJson codec's explicit handling of empty-object vs empty-list, numeric map keys, and opaque JSON values is a real correctness win — those are the classic silent-corruption cases in PHP json_encode/json_decode round-trips. The absence of dependencies, manifest constraints, or workflow changes lowers supply-chain and CI risk considerably.
Technical risk: Three material risks.
PageRenderer. The data-cms handle moved from an always-div to whichever semantic element the node maps to (p, h1–h6, a, ul/ol, landmark). Any consumer CSS/JS selector of the form div[data-cms] will silently stop matching — styling and behavioral hooks will break with no error. Any consumer relying on div as the layout box will also inherit browser default margins for headings, paragraphs, and lists. This is the single highest-impact item in the release and it fails quietly, which is the worst failure mode.fancy-cms-ui 0.8 with a very specific cross-runtime test matrix (28 sequences, 1,090 intermediate docs, inverse replays, 520 key cases, 14,200 number strings, seed 0x5eedc0de). That is impressive rigor, but it also means the two runtimes must be version-locked. A UI upgrade to 0.9 without a matching PHP release risks divergent documents — clients and server disagreeing about what a document means.Key questions:
fancy-cms-ui 0.8.x, and will breaking UI versions be gated?seq)?Stack fit: Treat this as a domain library inside a Laravel app, not as a framework feature. Recommended placement: app/Domain/Cms or a dedicated internal package, wired through a thin service provider of your own that binds a PageRepository interface to Eloquent. Laravel supplies what the package deliberately omits: request validation, authorization policies per page, transactions wrapping PageDoc::apply, and job queues for any expensive render/export. The pure-reducer design means the write path is naturally transaction-friendly: load doc → apply one op → persist doc + op row + new seq in one transaction. Note seq only increments on applied edits, so optimistic concurrency is straightforward — compare-and-swap on seq.
Migration path: The PageRenderer change is the only migration work, and it is selector-level, not data-level. Because the change is confined to which element carries [data-cms], the document data does not need rewriting. Concrete steps:
div[data-cms] selector across CSS, JS, tests, and any downstream consumers (email templates, PDF exports, SSR snapshots).[data-cms] before upgrading, so the CSS works under both v0.3 and v0.4 renderers. This makes the change backwards-compatible in practice and lets you ship in either order.h1–h6, p, ul/ol) where existing layout relied on div box behavior.Compatibility: No dependency changes, no manifest constraints, no workflow changes — this is a low-friction upgrade from an infrastructure standpoint. The real compatibility surface is (a) the renderer selector contract, and (b) the fancy-cms-ui 0.8 pairing. Pin both in composer.json/package.json and add a CI check that asserts the pinned pair.
Sequencing:
div[data-cms] usage; land the [data-cms] widening and semantic-element reset CSS on the current version.fancy-cms-ui 0.8 pin; verify PHP/JS parity in staging by replaying a sample of production documents through both runtimes and diffing (key order is the only expected normalization, per the notes).PageJson codec on all inbound and stored JSON paths before enabling new write features. This is the highest-value, lowest-risk item and closes the empty-object/numeric-key corruption class of bugs.PageDoc::apply as the single write boundary for all edits, with validation errors mapped to structured field errors in your API layer.Maintenance: Low and predictable. Zero dependencies means no transitive CVE churn and no lockfile drift — unusual and welcome. The maintenance burden shifts to your glue code (storage, endpoints, admin UI) rather than the library. Budget ongoing effort for keeping the PHP package and fancy-cms-ui version-locked; that pairing is the one thing that will generate recurring toil if left unmanaged. The pure-reducer core is also easy to reason about in code review, which keeps reviewer cost down.
Support: The release notes read like a team that takes correctness seriously — tests were written red-first, and semantic/emitter/boundary controls were intentionally red before implementation. That is a strong signal for supportability. Weak spots: (1) the notes reference fancy-cms-ui behavior but there is no evidence of a published compatibility table, (2) no mention of docs or migration guide for the breaking change, (3) no mention of a deprecation policy. For a 0.x package, the breaking change is acceptable but it confirms pre-1.0 volatility — plan for one breaking change per minor version and do not let consumers couple tightly to renderer output.
Scaling: The reducer is pure and per-document, so write-path scaling is a matter of your persistence layer, not the library. Two practical constraints: (a) reduceAll and validation run in-request, and validation includes iterative cycle detection plus bounds on node count, JSON depth, and value count — which is exactly what protects you from pathological documents, so respect those bounds rather than raising them when authors complain; (b) renderer output is deterministic and content-addressed by document state, which makes HTTP/CDN caching and static export straightforward. Avoid full-document replay of long op logs on every read — snapshot periodically.
Failure modes:
div[data-cms] → semantic-element change. Highest likelihood, highest impact, no exception thrown. Mitigate with the pre-emptive selector widening plus a visual regression suite.h1–h6, p, ul/ol). Cosmetic but high-volume.PageJson and uses raw json_encode/json_decode — empty objects becoming arrays is the classic symptom.seq unchanged — correct, but the UI must not assume its local seq advanced, or it will desync on retry.Ramp-up: Low for consumption, moderate for contribution. A Laravel developer can render documents in an afternoon. Understanding the op/reducer model well enough to add node types or style tokens requires reading the reducer, the validators, and the JS counterpart together — the parity contract means changes must land on both sides with the cross-runtime gate green. Expect roughly one to two weeks for a mid-level engineer to be productive on the write path and validation, and considerably longer (and ideally paired with the UI team) before anyone should touch the op spine. Given the 0.x version and the explicit breaking change, I would not expose this directly to third-party integrators yet; keep it internal behind your own API until the parity contract and compatibility matrix are documented.
How can I help you explore Laravel packages today?