Editor modules declared in YAML by feature bundles; News module replaces the List module #153
Labels
No labels
bug
enhancement
needs-triage
ready-for-agent
ready-for-human
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/deckle#153
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Let a feature bundle, or a site owner, add an editor module declared in YAML (
editor/modules/<name>.yaml). The YAML says which page types the module manages, how their pages are shown (a list or cards), what each entry shows, and which controls (search, sort, filters, paging) surround them.The page tree stays beside the module. Selecting a page that may hold the entries lists them, and that page is where new ones are created.
The news bundle uses this to ship a News module, in which news items are listed, created and edited without leaving the module. The generic List module is removed. Re-running
harness addon an installed feature adds the bundle files the site lacks, so existing news sites get the module file.Current behavior
MODULESinweb/src/modules.tsx): Page, View, List and Media, plus the hidden element edit page. Neither a site nor a feature can add one. The rail, route repair andreturnvalidation all check module ids synchronously against that registry./api/treepayload, with columns Title, Type, Date and Status and the row actions Rename…, Enable/Disable and Delete…. A title opens the page module withreturnto the list (#149). The create dialog (title, required fields, fields markedcreate: true) is defined insideListModule.tsx.hideChildPageson a page type hides all of that type's child pages from the tree; they are "curated through the List module instead". The page module points such a section to the List ("Open list", an inline hint). #149's fallbacks for a hidden page (its back button, the landing after deleting it) lead to the parent's List.harness add <feature>on an installed feature (its receipt exists) changes nothing and reportsalready-installed. Files added to a bundle later therefore never reach sites that installed it earlier (README, "Not covered yet").Desired behavior
Module files
editor/modules/<name>.yaml.internal/sidecardirscan rules: one module per file, the file name is the module id, strict decoding.editor/modules/;harness addvendors them andharness removeremoves them, like any bundle file. A site owner, or an agent following AGENTS.md, can also write one by hand.page,view,mediaandedit;api: the server answers every/api/*path itself, so the editor can never show a module there;internal: a host in front of the editor may block/internal/*(deckle-hosted's proxy answers 404 there on editor hosts).Schema
There is no key for where entries live. The tree selection and the page types'
childrenlists decide that (see below).Field names refer to the fields the listed page types declare, plus three built-ins:
title;date(front-matterdate);status: the disabled marker (front-matterdraft) and the publish state (the git badge).A declared field named
titleordateis the same front-matter key as the built-in; its label is used wherever a label shows.Where a field may be used:
columnsand cardmeta: any field or built-in;image: an image field;title:titleor a text field;text: a text or textarea field;tags: a select or multiselect field;search:title, and text and textarea fields;sort:title,date, and text, number, datetime and select fields;filters:status, and select, multiselect and bool fields.Filter values:
statusoffers Enabled/Disabled. The publish state is shown but cannot be filtered on in v1.Load errors, each naming the file and the reason:
pageSizeoutside 1–100;pageTypesentry that is not a loaded page type;statusfield (it would shadow the built-in);childrenexplicitly names one of the module's page types. Such an entry could hold entries of its own module, and one URL would then mean both the list and the entry;The page tree in a feature module
The page tree stays as the module's left column, in a selection-only mode:
Page management stays in the Page module.
Which pages are selectable:
children(news: pages of typenews, whosechildrenis[news-item]).childrenkey, which allows any child, does not count; otherwise every such page would become a container.The module's URL follows the other page-tree modules:
/<module>/<lang>/<page path>. The path names one of two things:Route repair judges the path against the tree as usual.
When the URL names neither (for example, Home still selected after switching modules), the module asks for a container, naming the selectable type: "Select a News page in the tree". Nothing is selected automatically, consistent with the editor's rule that it never auto-selects a page (#33).
While an entry is open, its container is the tree's selected row (styling and
aria-current). The row is revealed and scrolled into view as in #149, whether or not the container hides its children.Switching modules keeps the page path, as today. An entry open in the Page module opens in the entry editor when you switch to News; a container opens in the Page module when you switch back.
Hiding managed pages from the tree
hideChildPageskeeps its place in the page-type schema, with a narrower meaning: a container whose type sets it hides its managed children from the page tree, in every module. A managed child is a child page whose effective type is one of the page types of a module the page is a container for. Its other children stay in the tree, and its row expands when any remain.hideChildPageshides nothing. So a hidden page always has a module that lists it. A site without the module file (news installed before this change,harness add newsnot re-run) still reaches its items in the tree./api/treepayload, because route repair and the link pickers read them. A hidden page, or anything beneath one, selects its nearest drawn ancestor in the tree (#149).The module screen
With a container selected, the module lists that page's child pages of the module's page types, in the declared views. Several views give a switcher.
Search, sort, filters and paging apply to the whole set, on the server:
searchfields;Search terms, filters, sort, the active view and the page number live in the URL query, so reload, Back and a shared link keep them.
Until the user picks a sort, entries follow
sort, else the tree's order of the container's children. For news that isdate desc: newest first, undated last.statusrenders as the List does today: thedisabledmarker and the published / modified / unpublished badge.A "New " button appears for each of the module's page types that the container's type allows as a child (singletons excluded).
returnto the list.An entry's title (in a list) or its card opens the entry inside the module, with
returnset to the list's full URL.A server endpoint serves the entries:
It reads the front matter of the container's matching children. Every datetime value goes out as RFC3339 (see #133), including a native TOML datetime.
Another endpoint lists the loaded modules and their load errors.
The entry editor, inside the module
One tab per item in
edit:The active tab lives in the URL query, so reload keeps it and element edit pages opened from Content return to Content.
The toolbar has:
Save and Save & close act on the Properties form from either tab. Switching tabs keeps unsaved edits. Leaving the entry with unsaved edits goes through the unsaved-changes guard.
Close and Save & close go to
return: the list with its search, filters, sort, view and page intact. Without areturn(a module switch, a link, a typed URL) they go to the entry's container.Element edit pages opened from Content return to the entry, using #149's
return.Renaming the open entry follows it to its new path and keeps its
return. Deleting it lands on itsreturn, else on its container.Modules from the server
returnvalidation know the feature modules as well as the built-ins. Nothing judges a module id before the module list has loaded.returnnames a module URL goes back there.News
editor/modules/news.yaml, along the lines of the example above, and its manifestversionbecomes 2.harness add newsnext steps that mention the List module point to the News module instead.List module removed
/list/…URLs andreturnvalues naminglistare treated like any unknown module: route repair lands on the page module, and a stalereturnis ignored.harness addfills gapsharness add <feature>on an installed feature writes every bundle file missing on disk, including one the receipt lists but the owner deleted.harness removewould delete the owner's own file.versionbecomes the bundle's current version.hugo.tomledits and page-type allowances are not re-applied.added, exit 0, in text and--jsonoutput:fileslists only the files it wrote;config,allowancesandnextStepsare empty;--jsoncontract staysformatVersion: 1.already-installed. This is how existing news sites geteditor/modules/news.yaml.Docs
scaffold/site/AGENTS.mdand the README document:hideChildPages;create: truefields; a new entry opens in the module);add, including the README's--jsontable (theaddedrow) and "Not covered yet".Decisions
hideChildPageshides only pages a module manages (maintainer, in triage).harness add newsis re-run. deckle-hosted never re-runs it.news.yamlmust still load, so the key would linger as a dead key.pageTypes, notentries: it says what the list holds, and leaves room for a laterdata:source (Hugo data files) beside it.statusfilter is the disabled marker only: filtering on the publish state would need git state on every query.apiandinternalare reserved module ids besides the built-ins:/api/*never reaches the editor, and a host may block/internal/*(deckle-hosted does).web/src/routing.ts).harness addfills gaps for installed features. The maintainer's choice, so that existing news sites get the module file.added(maintainer, in triage). deckle-hosted parses--jsonstrictly and rejects unknown statuses; reusingaddedkeeps it working unchanged. No deckle-hosted change is part of this work.harness addmeanscaffold/site/AGENTS.mdand the README change in the same pull request (CLAUDE.md rule).Key interfaces
Registry and shell:
MODULES/moduleById/ModuleDef(web/src/modules.tsx);ModuleRail, which readsMODULESdirectly;web/src/App.tsx):selectModule,returnFacts, the route-repair wiring;repairRoute's unknown-module rule (web/src/routerepair.ts);parseReturn/readReturn(web/src/routing.ts) andReturnContext(web/src/returnnav.ts).Built-ins stay in the registry and feature modules come from the server; all of these must see both.
The page-tree column:
PAGE_TREE_NAV(web/src/modules.tsx),TreePane/TreeRow(web/src/components/TreePane.tsx), andcreateTreeController(web/src/stores/treeController.ts: reveal,selectedRow). It needs a per-module selectable rule and a selection-only mode.TreeBrowser'sisSelectable(greyed label, live chevron) is the precedent.The hidden-page rule moves from "under a
hideChildPagessection" to "managed and hidden". It lives in:hidingSection/parentPage(web/src/pagemanage.ts);deleteLanding(web/src/stores/docheaderActions.ts);PagePane'sbackTargetandisListSection/openList;TreeRow'shidesChildren;HideChildPagesininternal/pagetree).Loader:
internal/sidecardiris the scan contract. A new loader package goes besideinternal/element,internal/pagetypeandinternal/backendlayout; it validates against the loaded page types.Server (
internal/server): the module-list and entries endpoints.internal/pagetree, front matter the way the settings read does (ReadSettings/fieldValueininternal/pageops/settings.go).fieldValueturns a native TOML datetime into Go'stime.Timestring (#133), whilepagetreealready formatsdateas RFC3339. #151 fixes the settings read, so share one conversion.SchemaErrorBanner(web/src/components/SchemaErrorBanner.tsx): a new family.Entry editor tabs:
PageSettings(web/src/components/PageSettings.tsx;expose,footerSave) for Properties, andPagePane(web/src/components/PagePane.tsx) for Content.PagePane's docheader is built in, so the element list has to render without it.The create dialog:
CreateDialog,hasHiddenFieldsandseedFieldValuesinweb/src/components/ListModule.tsx.Page actions and
return:pageActionItems(web/src/pageactions.ts),createDocheaderActions,RenameDialog,DeleteDialog,withReturn(web/src/routing.ts) anduseReturn(web/src/returnnav.ts).Gap filling:
internal/addcmd(Run,Result,Receipt,scanDestinations,featureHints) and the add output incmd/harness/features.go(addCmd,addResultOutput,printAddResult). The JSON contract is the README's "Machine-readable output".The news bundle:
scaffold/features/news/(feature.yaml, and the comments ineditor/pagetypes/news.yamlandnews-item.yaml).Acceptance criteria
A site with the news feature shows a "News" rail entry after the built-ins, and a site without it does not. A module file written by hand also appears.
A module file is reported in the error banner, naming the file, and adds no rail entry, when it has any of:
page,view,media,edit,api,internal, in any case);status;childrennames one of the module's types.Other modules still load.
Reloading a News module URL (a list, a filtered list, an entry) stays in the News module. An element edit page whose
returnnames a module URL goes back there.In the News module, the tree shows every page, but only pages whose type explicitly lists
news-iteminchildrencan be selected; the others are greyed out and still expandable.The News module's tree has no ⋯ or right-click menus, no drag and drop, and no inline rename or create.
Switching to News with a non-news page selected shows "Select a News page in the tree", and nothing is selected automatically.
Selecting a News page lists its news items, newest first, in the declared views; with two views a switcher appears. A second News page lists only its own items.
Search, sort, filters (category, status) and paging work over the whole set, and survive reload and Back.
Every datetime the entries endpoint serves is RFC3339, including a front-matter
datewritten as a native TOML datetime."New News item" creates the item under the selected News page and opens it in the module.
Opening an item shows its Properties and Content tabs inside the module, with its News page selected in the tree; reload keeps the tab.
Save & close and Close return to the list with its search, filters, sort, view and page.
Element edit pages opened from Content return to the item's Content tab.
Rename…, Enable/Disable, Delete…, View, Publish, Undo and Discard… work from the entry editor; Delete… lands on the list.
With the news module file present, news items are hidden from the tree in every module, while a child of a News page that is not a news item stays visible. With
editor/modules/news.yamldeleted, news items show in the tree.The List module and its affordances are gone, and the page module offers "Manage in News" on a News page.
A news item reached through the preview offers "Open in News". Its back-button fallback and post-delete landing go to its News page in the News module.
harness add newson a site that installed news before the module file existed:editor/modules/news.yaml;addedlisting just that file, in text and--json.Re-running
harness add newsleaves an existing file at a bundle path alone and does not record it. On an up-to-date site it is a no-op reportingalready-installed.harness remove newsremoves the module file.AGENTS.md and the README document module files, their schema and load errors, the tree behavior, the narrowed
hideChildPages, the create dialog's new home and the gap-fillingadd.No docs, hints or bundle comments still refer to the List module.
How to verify
make test. It buildsweb/distfirst, whichweb/embed.goneeds; a barego test ./...fails in a fresh worktree. The Go side covers:addcmdand the CLI output).go test ./internal/...was green at triage.cd web && npm test. It covers:return);The suite was green at triage: 60 files, 1693 tests.
In a browser against
harness serve, on a site withharness add news, a git remote (so publishing works) and two News pages:The #149 analysis used Playwright with the system Chromium.
Gap filling, on a site with news installed:
editor/modules/news.yamland its receipt entry, re-runharness add news, and check that the file and the receipt entry come back;Assumptions to check
PagePane's element list can render inside another module. It reads the selected page from the URL-backed selection, which under the module's route names the entry. But its docheader and some follows (properties, crumbs) assume the page module.Out of scope
data/*.yaml) are the obvious next source, since they are the closest thing Hugo has to records. The schema leaves room, but v1 manages pages only.harness add newsthere, existing hosted news sites keep their items in the tree and show "version 1 installed; version 2 current".Blocked by
None - can start immediately