feat(editor): multilingual editor 3/3 — page properties, page menu, pending changes, paste confirmation, Languages view, module lists #203

Merged
dominik.polakovics merged 6 commits from afk/200 into main 2026-10-11 16:34:26 +02:00

Third editor slice of the multilingual work: everything around the page. No engine changes.

Closes #200

What changes

  • Page properties of a translation: the translation's own title, address segment, menu title and description, each with the default-language value and Copy. The segment shows the resulting address live and shows the server's 422 on slug on the field. The home page has no segment field; a hint says a language's home is always /<code>/. Appearance and Hidden are read-only with the server's note. Access shows Enabled per language, the address in this language and the translation file. The Properties docheader has Hide/Show .

  • Page ⋯ menu:

    • Source page: "Translate to …" for each language the page lacks. "Delete…" names the translations deleted with it.
    • Connected translation: "Disconnect from " asks for confirmation first, since there is no reconnect.
    • Any translation: "Delete translation…" names the one file that goes and lands on the untranslated-page screen. The tree row's delete confirms a translation the same way.
    • Undo and Discard… tooltips name the file. The entry editor gets the same items.
  • Cut and paste: fetchMoveRefs sends toParentUid and reads translations {moved, dropped}. Before the paste, the dialog names the languages that move with the element and warns about each one that is dropped (no connected target translation, a free one, or an untranslated container). When anything is dropped the button reads "Move and drop " in danger style. A same-page paste into a container is checked too.

  • Pending changes: no lang on pending, publish-all or discard-all. There is one row per file, and a translation row has a language badge, names its file and opens that language. On a multilingual site the panel header says publishing takes every language together. DisabledLinksDialog shows the badge for linkedFrom entries that carry lang.

  • Languages view: a Columns / Languages toggle (UI state, not part of the URL). Each non-inherited slot gets one column per language:

    • the source;
    • a translation's dense cards, with icon-only Translate placeholders in a connected translation;
    • or "Not translated to " with Translate page….

    Each column has Open. Page answers are cached per language alongside the per-language trees.

  • Modules and categories: outside the default language the list shows translated titles and the default-language title in italics with its code for untranslated entries. It adds a Translation column with an inline Translate…. New is disabled with a hint. The ⋮ menu offers Translate to … and Delete translation….

  • Link picker: the chips and the picker show the target's address in the current language, plus a note when the link falls back to the default-language page.

  • AGENTS.md "Editor modules": a module list shows the selected language's entries, and New creates entries in the default language. No skill walks through these editor screens (checked with grep), so the skills are unchanged.

Notes for review

  • The engine's GET /api/page/delete-plan translations is a count. The Delete dialog names the languages from the per-language trees and falls back to the count when it can't.
  • "Translate to …" on module entries in the default language reads the cached per-language trees (they carry managed entries) instead of fetching separate per-language entry lists.
  • The Languages view lists top-level elements per slot, like the mockup. A nested untranslated element is handled in the Columns view.
  • DeleteTranslationDialog (module entries) and DeleteTranslationPageDialog (pages) are near-duplicates; they can be merged in a follow-up.

Verification

  • cd web && npx tsc --noEmit -p .: clean.
  • npx vitest run: 110 files, 2758 tests pass. The only edits to existing tests are the pending-changes request shape (HostHeader, PendingChanges, Publish, api).
  • npx vite build: OK.
  • go test ./...: pass, including the AGENTS.md and skills drift tests.
  • New tests:
    • PageFormTranslation
    • PageMenuTranslation and pageactionsL10n
    • PasteTranslations
    • LinkPickerLang
    • PendingChangesLang
    • LanguagesView and languagePages
    • ModuleTranslation
  • Not done: the manual harness serve walk-through on testdata/multilingual-site.

🤖 Generated with Claude Code

Third editor slice of the multilingual work: everything around the page. No engine changes. Closes #200 ## What changes - **Page properties of a translation**: the translation's own title, address segment, menu title and description, each with the default-language value and Copy. The segment shows the resulting address live and shows the server's 422 on `slug` on the field. The home page has no segment field; a hint says a language's home is always `/<code>/`. Appearance and Hidden are read-only with the server's note. Access shows Enabled per language, the address in this language and the translation file. The Properties docheader has Hide/Show <default>. - **Page ⋯ menu**: - Source page: "Translate to <label>…" for each language the page lacks. "Delete…" names the translations deleted with it. - Connected translation: "Disconnect from <default>" asks for confirmation first, since there is no reconnect. - Any translation: "Delete <label> translation…" names the one file that goes and lands on the untranslated-page screen. The tree row's delete confirms a translation the same way. - Undo and Discard… tooltips name the file. The entry editor gets the same items. - **Cut and paste**: `fetchMoveRefs` sends `toParentUid` and reads `translations {moved, dropped}`. Before the paste, the dialog names the languages that move with the element and warns about each one that is dropped (no connected target translation, a free one, or an untranslated container). When anything is dropped the button reads "Move and drop <labels>" in danger style. A same-page paste into a container is checked too. - **Pending changes**: no `lang` on pending, publish-all or discard-all. There is one row per file, and a translation row has a language badge, names its file and opens that language. On a multilingual site the panel header says publishing takes every language together. `DisabledLinksDialog` shows the badge for `linkedFrom` entries that carry `lang`. - **Languages view**: a Columns / Languages toggle (UI state, not part of the URL). Each non-inherited slot gets one column per language: - the source; - a translation's dense cards, with icon-only Translate placeholders in a connected translation; - or "Not translated to <label>" with Translate page…. Each column has Open. Page answers are cached per language alongside the per-language trees. - **Modules and categories**: outside the default language the list shows translated titles and the default-language title in italics with its code for untranslated entries. It adds a Translation column with an inline Translate…. New is disabled with a hint. The ⋮ menu offers Translate to <label>… and Delete <label> translation…. - **Link picker**: the chips and the picker show the target's address in the current language, plus a note when the link falls back to the default-language page. - **AGENTS.md "Editor modules"**: a module list shows the selected language's entries, and New creates entries in the default language. No skill walks through these editor screens (checked with grep), so the skills are unchanged. ## Notes for review - The engine's `GET /api/page/delete-plan` `translations` is a count. The Delete dialog names the languages from the per-language trees and falls back to the count when it can't. - "Translate to <label>…" on module entries in the default language reads the cached per-language trees (they carry managed entries) instead of fetching separate per-language entry lists. - The Languages view lists top-level elements per slot, like the mockup. A nested untranslated element is handled in the Columns view. - `DeleteTranslationDialog` (module entries) and `DeleteTranslationPageDialog` (pages) are near-duplicates; they can be merged in a follow-up. ## Verification - `cd web && npx tsc --noEmit -p .`: clean. - `npx vitest run`: 110 files, 2758 tests pass. The only edits to existing tests are the pending-changes request shape (`HostHeader`, `PendingChanges`, `Publish`, `api`). - `npx vite build`: OK. - `go test ./...`: pass, including the AGENTS.md and skills drift tests. - New tests: - `PageFormTranslation` - `PageMenuTranslation` and `pageactionsL10n` - `PasteTranslations` - `LinkPickerLang` - `PendingChangesLang` - `LanguagesView` and `languagePages` - `ModuleTranslation` - Not done: the manual `harness serve` walk-through on `testdata/multilingual-site`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
- GET /api/publish/pending, POST /api/publish-all and discard-all send no
  lang: publishing takes every language together.
- A translation file's row wears the language badge, names its own file
  and opens the page in that language; the panel head says publishing is
  site-wide on a multilingual site.
- The publish link check names a linking translation with its badge and
  file.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- General: the translation's own title, address segment, menu title and
  description, each with the default-language value and Copy; the
  segment previews the address it gives in this language, live; the home
  page has no segment and says a language's home is always /<code>/.
- Appearance and Hidden read-only with the server's note; Access shows
  the per-language Enabled, the address in this language and the
  translation file.
- The Properties docheader of a translation toggles the source values.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Outside the default language an entry list shows the translation's
  title, an untranslated entry's default-language title in italics with
  its code, and a Translation column (translated, <default> changed
  since, unpublished changes, not translated) with an inline Translate….
- New is disabled outside the default language, with a hint.
- An entry's ⋮ menu offers Translate to <label>… per language it lacks
  and, outside the default language, Delete <label> translation….
- AGENTS.md "Editor modules": the list is the selected language's, New
  creates in the default language.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- The cut-and-paste check sends toParentUid and reads the move-refs
  translations block: the dialog names the languages whose translation
  moves with the element and warns, before the paste, about each one it
  drops (no connected target translation, a free one, or an untranslated
  container); the confirm reads "Move and drop <labels>" in danger.
- A same-page paste into a container is checked too.
- Link and page chips show the target's address in the edited language
  and, for an untranslated target, that the link goes to the
  default-language page; the link picker shows the same.
- The language context tells a connected translation from a free one.
- Styles for the pending-changes badge, the paste warning, the link
  address and the module translation column.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Source page: Translate to <label>… per language it lacks; Delete…
  names the translations deleted with it.
- Connected translation: Disconnect from <default label> (confirmed, no
  reconnect) → POST /api/page/disconnect; the page re-renders free.
- Any translation: Delete <label> translation… names the one file that
  goes and lands on the untranslated-page screen; the tree row menu
  confirms it the same way.
- Undo and Discard… tooltips name the file on a multilingual site.
- The entry editor gets the same items, and its properties form the
  address preview.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
feat(editor): Languages view — every language side by side
All checks were successful
ci / build-and-test (pull_request) Successful in 15m10s
78eb6c5dbb
- A Columns / Languages toggle in the page docheader of a multilingual
  site (UI state, not in the URL).
- Per slot of the layout (inherited slots skipped) one column per
  language: the source, each translation's dense cards (a connected one
  with icon-only Translate placeholders at the untranslated elements'
  source positions), or "Not translated to <label>" with Translate
  page…; Open switches to that language's Columns view.
- One page fetch per language, cached with the per-language trees and
  invalidated on the same refresh.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Author
Owner

[autoland] verdict: pass

PASS — validated by the autoland lander.

Signal: labctl pr checks 203 aggregate success (ci / build-and-test, 15m10s: npm ci/build/test, go vet, go test incl. the skills and AGENTS.md drift tests, go build) on head 78eb6c5. Not re-run locally.

Conventions: Conventional Commits title and commits; Closes #200 present; branch is current with main (merge-base = origin/main 354a2ac), no conflicts.

Scope matches #200, web only (no engine changes; the slug placeholder the form reads already comes from internal/pageops/translation_settings.go):

  • Page properties of a translation: PageForm.tsx (source boxes and Copy, live address hint, home note, Access address and file), FormFields/controls (placeholder, hint).
  • Page menu: pageactions.ts translation actions, TranslationPageDialogs.tsx, DeleteDialog.tsx (delete-plan translations), TreePane, PageScreen, EntryEditor.
  • Paste confirmation: links.ts toParentUid and translations; PageContent.tsx moved/dropped wording and "Move and drop ".
  • Pending changes: publish.ts drops lang; PendingChanges.tsx per-file rows with language badges, header note; DisabledLinksDialog lang badge.
  • Languages view: LanguagesView.tsx, stores/languagePages.ts.
  • Module and category lists: ModuleScreen.tsx, ModuleTranslation.tsx.
  • Link picker: LinkPicker.tsx, controls.tsx.

Edits to existing tests are limited to the pending-changes request shape (HostHeader, PendingChanges, Publish, api), which the acceptance criteria allow. AGENTS.md "Editor modules" gained the module-list language paragraph. Skills are rightly unchanged: no skill step walks through these editor screens (checked the verify-site and edit-content language mentions). deckle-hosted does not call the changed client functions.

Note (non-blocking, disclosed in the PR, as on #202): the manual harness serve walk-through on testdata/multilingual-site has not been done.

[autoland] verdict: pass PASS — validated by the autoland lander. Signal: `labctl pr checks 203` aggregate **success** (ci / build-and-test, 15m10s: npm ci/build/test, go vet, go test incl. the skills and AGENTS.md drift tests, go build) on head 78eb6c5. Not re-run locally. Conventions: Conventional Commits title and commits; `Closes #200` present; branch is current with main (merge-base = origin/main 354a2ac), no conflicts. Scope matches #200, web only (no engine changes; the slug placeholder the form reads already comes from internal/pageops/translation_settings.go): - Page properties of a translation: PageForm.tsx (source boxes and Copy, live address hint, home note, Access address and file), FormFields/controls (placeholder, hint). - Page menu: pageactions.ts translation actions, TranslationPageDialogs.tsx, DeleteDialog.tsx (delete-plan `translations`), TreePane, PageScreen, EntryEditor. - Paste confirmation: links.ts `toParentUid` and `translations`; PageContent.tsx moved/dropped wording and "Move and drop <labels>". - Pending changes: publish.ts drops `lang`; PendingChanges.tsx per-file rows with language badges, header note; DisabledLinksDialog lang badge. - Languages view: LanguagesView.tsx, stores/languagePages.ts. - Module and category lists: ModuleScreen.tsx, ModuleTranslation.tsx. - Link picker: LinkPicker.tsx, controls.tsx. Edits to existing tests are limited to the pending-changes request shape (HostHeader, PendingChanges, Publish, api), which the acceptance criteria allow. AGENTS.md "Editor modules" gained the module-list language paragraph. Skills are rightly unchanged: no skill step walks through these editor screens (checked the verify-site and edit-content language mentions). deckle-hosted does not call the changed client functions. Note (non-blocking, disclosed in the PR, as on #202): the manual `harness serve` walk-through on testdata/multilingual-site has not been done.
Sign in to join this conversation.
No reviewers
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!203
No description provided.