Editor: rebuild the Page module screen on shared components — insert rows, drag and drop, categorised insert dialog, clipboard #171

Closed
opened 2026-10-08 21:46:14 +02:00 by dominik.polakovics · 0 comments

Parent

#168 — the editing screens overhaul, Direction A (page module). Mockup and decision log: docs/mockups/editing-screens/ (README items 1, 2, 4–7, 9; dist/deckle-page-module.html is the clickable reference), merged in #169.

Summary

Rebuild the Page module's content screen on a shared component layer: the backend layout grid with equal-height columns, TYPO3-style always-visible insert rows that double as drag-and-drop targets, a categorised insert dialog, a real element menu, cut/copy/paste within and across pages, Content/Properties as docheader tabs, the disabled-page banner, and a pending-changes panel in the top bar. PagePane.tsx (~2,260 lines) is replaced by small components that every later screen reuses.

Current behavior

  • web/src/components/PagePane.tsx renders either a flat indented list (no backend layout) or a CSS grid of slot cells, with gap/row/drop markup written twice; the insert picker's Structure/Content lists are copy-pasted; the ⋮ menu offers Move to column…, Move up/down, Duplicate, Delete…; inserting opens the "Insert element" dialog grouped by group (structure/content) and navigates to the new element's edit page; drag and drop uses dropzone.ts (before/after/into zones) and dragReorder.ts, and #127 reports drops in a row edge zone silently no-op.
  • Page properties open via ?props=1, swapping the list for PageSettings.
  • Inherited slots show greyed rows with "Inherited from X — edit there"; "Unused content" lists slots the current layout lacks.
  • The top bar shows a "N changes" chip and the Publish button; there is no list of what is pending.
  • Badges, strips and menus are re-implemented per screen (ModuleScreen.tsx duplicates PageBadge; four error-strip styles; the page ⋯ menu borrows element-menu classes).

Desired behavior

Everything below is shown in the mockup; where this text and the mockup differ, the mockup wins.

Shared component layer (web/src/components/, names as in the mockup's Components overlay): Docheader (two rows: button bar; crumbs + title + badge; optional tabs), ButtonBar, Tabs, Badge, Menu (popover with items, separators, danger items, keyboard: Escape closes, focus returns to the trigger; built on layers.ts), Dialog, LayoutGrid → SlotColumn → InsertRow + ElementCard, ElementPicker, ClipboardBar, PageBanner, PendingChanges. Styles use the tokens in web/src/styles.css (#72). The duplicated badge markup and the error-strip styles that this screen touches are replaced by the shared components; the legacy FormPane wrapper and the stale "no module has been migrated" note in Docheader.tsx go.

Layout grid

  • Columns of a layout row stretch to the row's height. A page without a backend layout is a LayoutGrid with one slot, so the flat list and the slot grid are one code path. Nested visible children of a block element (where a placement allows them) render as an indented sub-list inside the card, with their own insert rows, using the same components.
  • Each slot header shows label and count, plus an ⓘ listing the slot's elements allowlist when it has one.
  • Inherited slots render their ancestor's elements read-only (dashed cards, "From " badge) and a bottom line "Inherited from . Adding or pasting here replaces it on this page only. Edit on →" (the link opens that page). They have exactly one insert row, at the top; inserting or pasting there creates this page's own content for the slot.
  • "Unused content" stays a collapsible section below the grid with its existing actions, rendered with the same cards.

Insert rows

  • One InsertRow at the top of each editable slot and after every element. It is always visible: a centred small "+ Element" button in a fixed-height row (the row is as tall as the drop zone it becomes, so starting a drag shifts nothing).
  • Clicking it opens the insert dialog for that slot and position; after the server inserts, the editor opens the new element's form (today's behaviour).
  • While something is on the clipboard, a Paste button sits right of "+ Element" in every row; it is disabled, with the reason in its tooltip, where the slot's allowlist or the element's placement refuses that element.

Insert dialog (ElementPicker)

  • Left: categories with counts, All first and selected by default; above them a search box (matches label and description) focused on open. Right: tiles (icon, label, one-line description).
  • Categories come from a new optional element sidecar key category (a free label string; elements without one fall under Other; group: inline and hidden: true elements never appear). The scaffold declares: Text (hero, text, cta, quote, accordion), Media (text-media, image, video-embed), Lists & teasers (cards, page-teasers; the news bundle's news-list), Forms (contact-form), Structure (spacer). scaffold/site/AGENTS.md documents the key.
  • Elements the slot or the placement rules refuse stay visible but disabled, with "Not allowed here" on the tile; a note above the grid names what the slot allows. emptyPickerReason becomes this per-tile reason. insertable: false elements do not appear at all (unchanged).

Drag and drop

  • Cards are draggable by their handle. During a drag every insert row becomes a drop zone (dashed, "Move here" on the hovered one); rows whose slot refuses the element type go grey and refuse the drop; the inherited slot's single row accepts a drop and overrides. Dropping calls the existing move operation with the target slot and index. Empty slots have their one row as the target. There are no other drop zones: this replaces dropzone.ts's before/after/into edge zones, and with it the silent no-op of #127 (reference it in the PR; do not close it from this issue).
  • Keyboard alternative: Move up / Move down in the menu.

Element card and menu

  • Card: handle, type icon, label, #uid, Disabled badge (from enabled, see the blocked-by issue), two-line summary of the element's content, thumbnails for image items; hover/focus reveals Edit, Enable/Disable, ⋮. Single click or Enter opens the form.
  • ⋮ menu: Edit, Enable/Disable, Duplicate, —, Cut, Copy, —, Move up, Move down, —, Delete… (keeps the existing confirmation and linked-pages warning). Move to column… is removed.
  • Page ⋯ menu (docheader): Rename…, New subpage…, Enable/Disable page, —, Delete…; same Menu component.

Clipboard

  • Cut and Copy put one element on a clipboard that survives navigation between pages in this browser tab (sessionStorage, like the tree width). A cut card shows dashed with a "Cut" badge; a copied one shows "Copied". A ClipboardBar above the grid names the element and its source page, with Clear.
  • Paste after cut moves the element (within a page: the move op; to another page: a cross-page move); paste after copy inserts a copy with a fresh uid (within a page: duplicate; to another page: a cross-page copy). Both clear the clipboard after one paste. Paste respects allowlists and placement. Shortcuts: ⌘/Ctrl+X and +C on a focused card, ⌘/Ctrl+V on a focused insert row.
  • The server gains the cross-page operations (extend POST /api/element/move and /duplicate with a target page, or add dedicated ops — implementer's call); media references and links inside the element travel unchanged; the result updates both pages' trees and is one git snapshot like every save.

Page level

  • The docheader of a page shows tabs Content (with element count) and Properties; Properties shows today's PageSettings in place (the ?props=1 route goes away; the form screen issue rebuilds the form itself).
  • A disabled page shows a PageBanner above the grid: "This page is disabled. It is not built into the site, and links to it are reported before publishing." with Enable page.
  • Undo's tooltip names what it will undo ("Undo: Text & media saved 2 min ago") when the API can say so.
  • The "N changes" chip in the top bar opens a PendingChanges panel: the pages and media files with saved, unpublished changes, one line each ("Services — 2 elements changed, 1 added"; "Hosting — new page, not published yet"; "assets/media/… — added"), each row opens the page or the Media module; Publish all (today's Publish) and Discard all… at the bottom.

Still true afterwards: the entry editor's Content tab embeds the same grid without chrome (#153); feature modules (News, Categories) keep working; hover on a card still posts the highlight to the preview bridge; the hosted header slot (#139) is untouched.

Decisions

  • Insert rows are always visible, one per position, fixed height, centred — TYPO3's "+ Content" (README item 4); hover-revealed gaps were tried and rejected.
  • Insert rows are the only drop targets (README item 5) — one visible affordance for both insert and move, no hidden edge zones, allowlists shown while dragging.
  • "Move to column…" is removed — drag and drop and cut/paste cover it (README item 6).
  • The clipboard holds one entry and clears after one paste, cut and copy alike (README item 7) — no stale entries; multi-element clipboards are out.
  • category is a free label on the sidecar, not an enum — bundles and site owners name their own categories; "All" and "Other" are the only built-ins.
  • Inherited slots get a single override row (README item 4) — the editor must be able to replace inherited content without leaving the page.
  • The flat list is a one-slot grid — gap/row/drop markup exists once.

Key interfaces

  • web/src/components/PagePane.tsx → replaced by LayoutGrid, SlotColumn, InsertRow, ElementCard, ElementPicker; elementcrud.ts (allowedElements, placementAllowed, emptyPickerReason, moveToIndex, DropVerdict) keeps the rules; dropzone.ts is retired; dragReorder.ts may stay as the drag state machine.
  • web/src/api/elementops.ts (insertElement, moveElement, duplicateElement) and their handlers in internal/server / internal/elemops — cross-page variants.
  • internal/element/schema.go — the category sidecar key (Category string), surfaced through web/src/api/elements.ts.
  • internal/backendlayout — slot elements allowlist and inherit; the override semantics when a page gains its own content in an inherited slot.
  • web/src/layers.ts (openLayer, Escape order) for Menu, Dialog, the pending-changes panel.
  • web/src/api/publish.ts / internal/server/publish.go — the pending-changes listing.
  • scaffold/site/AGENTS.md — category; internal/element/agentsdoc_test.go.

Acceptance criteria

  • PagePane.tsx is gone; the Page module content screen, the entry editor's Content tab, and the no-layout case all render through LayoutGrid.
  • In a two-column layout, Main and Sidebar have equal height within their row.
  • Every editable slot shows one "+ Element" row at the top and one after each element, each centred and of the same height at rest and while dragging; inserting from the row after element 2 places the new element at position 3 and opens its form.
  • The insert dialog shows All plus the sidecar categories with counts and a search box; elements the slot refuses are visible but disabled with the reason; the scaffold sidecars carry category and AGENTS.md documents it; the drift test passes.
  • Dragging a card highlights every insert row as a drop zone, greys rows whose slot refuses the type, and dropping moves the element to that slot and index (verified in the saved markdown); the inherited footer's single row accepts a text element and the page then has its own footer content.
  • The element menu is Edit, Enable/Disable, Duplicate, Cut, Copy, Move up, Move down, Delete…; "Move to column…" no longer exists anywhere in web/.
  • Cut on page A, open page B in the tree, Paste in a row: the element is gone from A and present at that position on B, in one snapshot; Copy then Paste on another page inserts a copy with a new uid; the clipboard is empty after either paste; Paste is disabled where the allowlist refuses.
  • Content and Properties are docheader tabs; ?props=1 is gone from routing.ts.
  • A disabled page shows the banner with a working Enable page button.
  • The "N changes" chip opens the pending-changes panel listing pages and media; rows navigate; Publish all publishes.
  • Hover on a card still posts the highlight to the preview bridge; feature module screens and the hosted header still pass their tests.

How to verify

  • make test; the existing PagePane/ElementCrud/dropzone tests are replaced by tests for the new components (insert positions, drop verdicts per slot allowlist, clipboard transitions, menu contents).
  • make run on testdata/site: walk the flows in the acceptance criteria and compare with docs/mockups/editing-screens/dist/deckle-page-module.html (sh wrap.sh to view it).
  • git log/git diff on the site after a cross-page cut-paste: one commit, both files changed as expected.

Assumptions to check

  • Whether the engine already supports a page owning content in a slot the layout marks inherit (what the slot wrapper looks like then); if not, implement it as part of the override row.
  • Whether any scaffold or testdata element can contain visible block children today (vs hidden items); if none, the nested sub-list only needs to exist for the no-layout tree.
  • Whether publish.go can enumerate pending changes per page/media cheaply (it already diffs content/ and assets/media/ to publish); the panel needs that list.
  • Whether the undo snapshot message is available to the client for the tooltip; if not, the plain tooltip stays.
  • deckle-hosted (read-only import) embeds the editor: check that nothing there depends on ?props=1 or the PagePane DOM.

Out of scope

  • The element form screen and the page properties form rebuild (tabs General/Appearance/Access, validation at rest, unsaved-changes guard, ⌘S, Preview button) — third issue. This issue keeps EditPane/PageSettings as they are and only routes to them.
  • Multi-select, compact/detailed card toggle, a "Recent" category, language switch, keyboard reorder with ⌘↑/↓ (README "not built").
  • The View and Media modules.
  • Changing the publish flow itself beyond listing what is pending.

Blocked by

## Parent #168 — the editing screens overhaul, Direction A (page module). Mockup and decision log: `docs/mockups/editing-screens/` (README items 1, 2, 4–7, 9; `dist/deckle-page-module.html` is the clickable reference), merged in #169. ## Summary Rebuild the Page module's content screen on a shared component layer: the backend layout grid with equal-height columns, TYPO3-style always-visible insert rows that double as drag-and-drop targets, a categorised insert dialog, a real element menu, cut/copy/paste within and across pages, Content/Properties as docheader tabs, the disabled-page banner, and a pending-changes panel in the top bar. `PagePane.tsx` (~2,260 lines) is replaced by small components that every later screen reuses. ## Current behavior - `web/src/components/PagePane.tsx` renders either a flat indented list (no backend layout) or a CSS grid of slot cells, with gap/row/drop markup written twice; the insert picker's Structure/Content lists are copy-pasted; the ⋮ menu offers Move to column…, Move up/down, Duplicate, Delete…; inserting opens the "Insert element" dialog grouped by `group` (structure/content) and navigates to the new element's edit page; drag and drop uses `dropzone.ts` (before/after/into zones) and `dragReorder.ts`, and #127 reports drops in a row edge zone silently no-op. - Page properties open via `?props=1`, swapping the list for `PageSettings`. - Inherited slots show greyed rows with "Inherited from X — edit there"; "Unused content" lists slots the current layout lacks. - The top bar shows a "N changes" chip and the Publish button; there is no list of what is pending. - Badges, strips and menus are re-implemented per screen (`ModuleScreen.tsx` duplicates `PageBadge`; four error-strip styles; the page ⋯ menu borrows element-menu classes). ## Desired behavior Everything below is shown in the mockup; where this text and the mockup differ, the mockup wins. **Shared component layer** (`web/src/components/`, names as in the mockup's Components overlay): `Docheader` (two rows: button bar; crumbs + title + badge; optional tabs), `ButtonBar`, `Tabs`, `Badge`, `Menu` (popover with items, separators, danger items, keyboard: Escape closes, focus returns to the trigger; built on `layers.ts`), `Dialog`, `LayoutGrid` → `SlotColumn` → `InsertRow` + `ElementCard`, `ElementPicker`, `ClipboardBar`, `PageBanner`, `PendingChanges`. Styles use the tokens in `web/src/styles.css` (#72). The duplicated badge markup and the error-strip styles that this screen touches are replaced by the shared components; the legacy `FormPane` wrapper and the stale "no module has been migrated" note in `Docheader.tsx` go. **Layout grid** - Columns of a layout row stretch to the row's height. A page without a backend layout is a `LayoutGrid` with one slot, so the flat list and the slot grid are one code path. Nested visible children of a block element (where a placement allows them) render as an indented sub-list inside the card, with their own insert rows, using the same components. - Each slot header shows label and count, plus an ⓘ listing the slot's `elements` allowlist when it has one. - Inherited slots render their ancestor's elements read-only (dashed cards, "From <page>" badge) and a bottom line "Inherited from <page>. Adding or pasting here replaces it on this page only. Edit on <page> →" (the link opens that page). They have exactly one insert row, at the top; inserting or pasting there creates this page's own content for the slot. - "Unused content" stays a collapsible section below the grid with its existing actions, rendered with the same cards. **Insert rows** - One `InsertRow` at the top of each editable slot and after every element. It is always visible: a centred small "+ Element" button in a fixed-height row (the row is as tall as the drop zone it becomes, so starting a drag shifts nothing). - Clicking it opens the insert dialog for that slot and position; after the server inserts, the editor opens the new element's form (today's behaviour). - While something is on the clipboard, a **Paste** button sits right of "+ Element" in every row; it is disabled, with the reason in its tooltip, where the slot's allowlist or the element's placement refuses that element. **Insert dialog** (`ElementPicker`) - Left: categories with counts, **All** first and selected by default; above them a search box (matches label and description) focused on open. Right: tiles (icon, label, one-line description). - Categories come from a new optional element sidecar key `category` (a free label string; elements without one fall under **Other**; `group: inline` and `hidden: true` elements never appear). The scaffold declares: Text (hero, text, cta, quote, accordion), Media (text-media, image, video-embed), Lists & teasers (cards, page-teasers; the news bundle's news-list), Forms (contact-form), Structure (spacer). `scaffold/site/AGENTS.md` documents the key. - Elements the slot or the placement rules refuse stay visible but disabled, with "Not allowed here" on the tile; a note above the grid names what the slot allows. `emptyPickerReason` becomes this per-tile reason. `insertable: false` elements do not appear at all (unchanged). **Drag and drop** - Cards are draggable by their handle. During a drag every insert row becomes a drop zone (dashed, "Move here" on the hovered one); rows whose slot refuses the element type go grey and refuse the drop; the inherited slot's single row accepts a drop and overrides. Dropping calls the existing move operation with the target slot and index. Empty slots have their one row as the target. There are no other drop zones: this replaces `dropzone.ts`'s before/after/into edge zones, and with it the silent no-op of #127 (reference it in the PR; do not close it from this issue). - Keyboard alternative: Move up / Move down in the menu. **Element card and menu** - Card: handle, type icon, label, `#uid`, Disabled badge (from `enabled`, see the blocked-by issue), two-line summary of the element's content, thumbnails for image items; hover/focus reveals Edit, Enable/Disable, ⋮. Single click or Enter opens the form. - ⋮ menu: Edit, Enable/Disable, Duplicate, —, Cut, Copy, —, Move up, Move down, —, Delete… (keeps the existing confirmation and linked-pages warning). **Move to column… is removed.** - Page ⋯ menu (docheader): Rename…, New subpage…, Enable/Disable page, —, Delete…; same `Menu` component. **Clipboard** - Cut and Copy put one element on a clipboard that survives navigation between pages in this browser tab (sessionStorage, like the tree width). A cut card shows dashed with a "Cut" badge; a copied one shows "Copied". A `ClipboardBar` above the grid names the element and its source page, with Clear. - Paste after cut moves the element (within a page: the move op; to another page: a cross-page move); paste after copy inserts a copy with a fresh uid (within a page: duplicate; to another page: a cross-page copy). Both clear the clipboard after one paste. Paste respects allowlists and placement. Shortcuts: ⌘/Ctrl+X and +C on a focused card, ⌘/Ctrl+V on a focused insert row. - The server gains the cross-page operations (extend `POST /api/element/move` and `/duplicate` with a target page, or add dedicated ops — implementer's call); media references and links inside the element travel unchanged; the result updates both pages' trees and is one git snapshot like every save. **Page level** - The docheader of a page shows tabs **Content** (with element count) and **Properties**; Properties shows today's `PageSettings` in place (the `?props=1` route goes away; the form screen issue rebuilds the form itself). - A disabled page shows a `PageBanner` above the grid: "This page is disabled. It is not built into the site, and links to it are reported before publishing." with **Enable page**. - Undo's tooltip names what it will undo ("Undo: Text & media saved 2 min ago") when the API can say so. - The "N changes" chip in the top bar opens a `PendingChanges` panel: the pages and media files with saved, unpublished changes, one line each ("Services — 2 elements changed, 1 added"; "Hosting — new page, not published yet"; "assets/media/… — added"), each row opens the page or the Media module; **Publish all** (today's Publish) and **Discard all…** at the bottom. **Still true afterwards**: the entry editor's Content tab embeds the same grid without chrome (#153); feature modules (News, Categories) keep working; hover on a card still posts the highlight to the preview bridge; the hosted header slot (#139) is untouched. ## Decisions - Insert rows are always visible, one per position, fixed height, centred — TYPO3's "+ Content" (README item 4); hover-revealed gaps were tried and rejected. - Insert rows are the only drop targets (README item 5) — one visible affordance for both insert and move, no hidden edge zones, allowlists shown while dragging. - "Move to column…" is removed — drag and drop and cut/paste cover it (README item 6). - The clipboard holds one entry and clears after one paste, cut and copy alike (README item 7) — no stale entries; multi-element clipboards are out. - `category` is a free label on the sidecar, not an enum — bundles and site owners name their own categories; "All" and "Other" are the only built-ins. - Inherited slots get a single override row (README item 4) — the editor must be able to replace inherited content without leaving the page. - The flat list is a one-slot grid — gap/row/drop markup exists once. ## Key interfaces - `web/src/components/PagePane.tsx` → replaced by `LayoutGrid`, `SlotColumn`, `InsertRow`, `ElementCard`, `ElementPicker`; `elementcrud.ts` (`allowedElements`, `placementAllowed`, `emptyPickerReason`, `moveToIndex`, `DropVerdict`) keeps the rules; `dropzone.ts` is retired; `dragReorder.ts` may stay as the drag state machine. - `web/src/api/elementops.ts` (`insertElement`, `moveElement`, `duplicateElement`) and their handlers in `internal/server` / `internal/elemops` — cross-page variants. - `internal/element/schema.go` — the `category` sidecar key (`Category string`), surfaced through `web/src/api/elements.ts`. - `internal/backendlayout` — slot `elements` allowlist and `inherit`; the override semantics when a page gains its own content in an inherited slot. - `web/src/layers.ts` (`openLayer`, Escape order) for `Menu`, `Dialog`, the pending-changes panel. - `web/src/api/publish.ts` / `internal/server/publish.go` — the pending-changes listing. - `scaffold/site/AGENTS.md` — `category`; `internal/element/agentsdoc_test.go`. ## Acceptance criteria - [ ] `PagePane.tsx` is gone; the Page module content screen, the entry editor's Content tab, and the no-layout case all render through `LayoutGrid`. - [ ] In a two-column layout, Main and Sidebar have equal height within their row. - [ ] Every editable slot shows one "+ Element" row at the top and one after each element, each centred and of the same height at rest and while dragging; inserting from the row after element 2 places the new element at position 3 and opens its form. - [ ] The insert dialog shows All plus the sidecar categories with counts and a search box; elements the slot refuses are visible but disabled with the reason; the scaffold sidecars carry `category` and AGENTS.md documents it; the drift test passes. - [ ] Dragging a card highlights every insert row as a drop zone, greys rows whose slot refuses the type, and dropping moves the element to that slot and index (verified in the saved markdown); the inherited footer's single row accepts a text element and the page then has its own footer content. - [ ] The element menu is Edit, Enable/Disable, Duplicate, Cut, Copy, Move up, Move down, Delete…; "Move to column…" no longer exists anywhere in `web/`. - [ ] Cut on page A, open page B in the tree, Paste in a row: the element is gone from A and present at that position on B, in one snapshot; Copy then Paste on another page inserts a copy with a new uid; the clipboard is empty after either paste; Paste is disabled where the allowlist refuses. - [ ] Content and Properties are docheader tabs; `?props=1` is gone from `routing.ts`. - [ ] A disabled page shows the banner with a working Enable page button. - [ ] The "N changes" chip opens the pending-changes panel listing pages and media; rows navigate; Publish all publishes. - [ ] Hover on a card still posts the highlight to the preview bridge; feature module screens and the hosted header still pass their tests. ## How to verify - `make test`; the existing `PagePane`/`ElementCrud`/`dropzone` tests are replaced by tests for the new components (insert positions, drop verdicts per slot allowlist, clipboard transitions, menu contents). - `make run` on `testdata/site`: walk the flows in the acceptance criteria and compare with `docs/mockups/editing-screens/dist/deckle-page-module.html` (`sh wrap.sh` to view it). - `git log`/`git diff` on the site after a cross-page cut-paste: one commit, both files changed as expected. ## Assumptions to check - Whether the engine already supports a page owning content in a slot the layout marks `inherit` (what the slot wrapper looks like then); if not, implement it as part of the override row. - Whether any scaffold or testdata element can contain *visible* block children today (vs hidden items); if none, the nested sub-list only needs to exist for the no-layout tree. - Whether `publish.go` can enumerate pending changes per page/media cheaply (it already diffs `content/` and `assets/media/` to publish); the panel needs that list. - Whether the undo snapshot message is available to the client for the tooltip; if not, the plain tooltip stays. - `deckle-hosted` (read-only import) embeds the editor: check that nothing there depends on `?props=1` or the `PagePane` DOM. ## Out of scope - The element form screen and the page properties form rebuild (tabs General/Appearance/Access, validation at rest, unsaved-changes guard, ⌘S, Preview button) — third issue. This issue keeps `EditPane`/`PageSettings` as they are and only routes to them. - Multi-select, compact/detailed card toggle, a "Recent" category, language switch, keyboard reorder with ⌘↑/↓ (README "not built"). - The View and Media modules. - Changing the publish flow itself beyond listing what is pending. ## Blocked by - #170
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Cloonar/deckle#171
No description provided.