glueful / thallo-navigation
Navigation menus for Thallo: menu trees as data with published-only resolution, as a removable capability pack.
Requires
- php: ^8.3
- glueful/extension-contracts: *
- glueful/framework: ^1.65.2
- glueful/thallo-contracts: v1.0.0-beta.22
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-12 23:39:18 UTC
README
Navigation menus as data for Thallo — menu trees stored once,
served headless through a public API, and consumed by themes through the MenuReader
contract — packaged as a removable capability pack (V2 rendered-delivery sub-project 1;
see docs/internal/V2_DESIGN.md). The future thallo-render pack consumes menus optionally:
menu('main') yields [] when this pack is absent or disabled.
What it provides
-
Two tables:
navigation_menus(identity +lock_versionfor optimistic tree writes) andnavigation_items(tree nodes:entryitems as soft references — no cross-package FKs — or rawurlitems, with per-locale label maps). -
Resolution semantics (
MenuReader::menu(slug, locale)):Rule Behavior Labels fallback chain: requested locale → site default → any available urlitemsserved verbatim entryitemsresolved to the live public path at read time via EntryTargetResolver— slug changes propagate automaticallyNon-published targets item and its subtree omitted ( unpublished,routeless,deleted,missing) — no dead links can ever renderUnknown menu / disabled capability null— indistinguishable from "pack absent" -
EntryTargetResolver(contract added inthallo-contracts, implemented by core):resolve(entryUuid, locale)→{status: published|unpublished|deleted|missing|routeless, path}—publishedmeans addressable (publication AND route);routelessis the actionable "assign a route" state;pathis null for every non-published status.
HTTP API
Public (rate-limited): GET /v1/menus/{slug}?locale=en — the resolved published-only tree.
Admin (capability → auth → content_permission:navigation.manage), under
/v1/admin/navigation: menu CRUD (GET|POST /menus, GET|PUT|DELETE /menus/{slug}) and
the atomic whole-tree replace PUT /menus/{slug}/items — the body carries the
lock_version from the editor's GET; a stale version is a 409 (reload and retry).
The admin tree read is locale-aware (?locale=): target_status/target_url are
resolved for that locale, so editor badges always match the locale on screen. Tree
payloads are validated recursively: kinds, http(s):// or site-relative URLs, labels
≤ 200 chars, depth ≤ 6, ≤ 500 items; missing/deleted targets are 422s while
unpublished/routeless are allowed (editors build menus while content is in draft).
MenuUpdated is dispatched on every mutation — the render-cache purge seam.
Admin SPA
Settings-level Navigation page (capability-gated): menu list plus a tree editor —
per-locale labels via a locale switcher (which also drives the target badges), entry
picker with unpublished/routeless/deleted/missing badges, URL items, and
up/down/indent/outdent reordering. Saving replaces the whole tree under lock_version.
Install / remove
Bundled by default in the Thallo create-project template. Existing app:
composer require glueful/thallo-navigation, ./thallo extensions:enable thallo-navigation,
./thallo migrate:run. Disable via the switchboard
(config/thallo.php: 'capabilities' => ['thallo.navigation' => false]) or remove entirely —
routes 404, MenuReader resolves null, core and every other pack boot unchanged.
Out of scope (v1)
Menu-item visibility rules (auth-based), mega-menu metadata (icons, badges), theme menu-region mapping beyond slugs, drag-drop editing polish, and per-item target/rel attributes — all can layer onto the json columns without schema breaks.