---
title: "ADR-266: Move, copy, delete and translate act in the workspace the run is in"
manual: "LLM Foundation"
version: "main"
permalink: "https://docs.typo3.org/permalink/netresearch/nr-llm:adr-266@main"
source: "Adr/Adr266TheStructuralWritersActInTheWorkspace.rst"
rendered: "2026-11-12T15:41:35+00:00"
---

# ADR-266: Move, copy, delete and translate act in the workspace the run is in {#adr-266}

-   *Status:* Accepted
-   *Date:* 2026-11-05
-   *Amends:* [ADR-264](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-264@main) (its consequence that the move, copy, delete and translation writers still refuse a draft workspace after the card), [ADR-262](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-262@main) (its consequence that move, copy, delete and localize refuse a draft workspace after the card; its reliance on `execute()` asking the refusal again when a turn holding a refused call is approved), [ADR-135](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-135@main) (the global backend user's workspace is set to the run's around the five writers' calls), [ADR-198](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-198@main) (live rows only, for the four writers named here in a run's recorded draft workspace), [ADR-146](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-146@main) (`move_content_element` and `create_translation_draft` in the live workspace only)
-   *Authors:* Netresearch DTT GmbH

## Context {#context}

[ADR-260](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-260@main) and [ADR-262](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-262@main) let the writers that
change fields write a workspace version in the draft workspace a run was
started in. The writers that change the structure — `move_content_element`,
`move_page`, `copy_record`, `delete_record` and
`create_translation_draft` — still refused a draft workspace, after the
card.

Core's commands do different things in a workspace. Measured on
2026-11-05 with `ToolDataHandler` and an administrator put into a draft
workspace, on TYPO3 14.3.10 and 13.4.37 (SQLite) and 14.3.10 (MariaDB),
every row of `pages` and `tt_content` dumped after each command, with
the `workspace` aspect of TYPO3's Context set to that workspace as the
tools now pin it (an earlier round measured with the aspect left at live,
see Consequences):

-   **move** (`DataHandler::moveRecord()`): a version of the record with
    `t3ver_state` 4 carrying the new `pid`, `sorting` and, from the paste
    `update`, the new `colPos`; its translations get a move version too.
    "After element X" resolves to X's place in the workspace, a target page
    new in the workspace is accepted, and a record new in the workspace is
    moved in place. Live is unchanged.
-   **copy** (`copyRecord()`, `copyPages()`): every copied row is new in
    the workspace (`t3ver_state` 1), copied from the workspace's version of
    the source; a page's content is copied as the workspace shows it, with the
    translations of its elements.
-   **delete** (`deleteAction()`): a live record becomes a delete version
    (`t3ver_state` 2), and so do its translations; a record changed in the
    workspace has its version turned into one. A page is marked with its
    translation only: its subpages and content get no version, and publishing
    the workspace deletes the live branch. Two cases are not a deletion: a
    record **new** in the workspace is discarded — removed from the database,
    beyond the recycler — and a record **moved** there has its move version
    discarded: it is back at its live place and nothing is deleted.
-   **localize** (`localize()`): the translation is new in the workspace,
    made from the workspace's version of the source and placed where the
    workspace shows it. Deleting an existing translation and localizing again
    — this tool's `overwrite` — differs between the cores: 14.3.10 marks the
    old translation and creates the new one; 13.4.37 marks the old one and
    then refuses the localization ("There already are localizations").

Core resolves the version of a moved or deleted translation against the
parent's version: its `l18n_parent` names the parent's version uid.

## Decision {#decision}

**The five writers act in the run's draft workspace**, as the field writers
do under [ADR-260](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-260@main): they refuse a draft workspace only where
the run did not record it, and their DataHandler, started with the pinned
user, produces what core produces there. Live is not touched.

**They run in that workspace.** `execute()`, the card and the refusal
before approval set the `workspace` aspect of TYPO3's Context and the
workspace of the global backend user to the acting user's, and put both back
(`AsksItsRefusalFirstTrait`). Core reads both: the site lookup reads the
rootline through the aspect, which decides whether a page new in the
workspace has a site and so whether a copy brings its elements' translations
and a record there can be translated; `workspaceOL()` and, in TYPO3 13.4,
the page translations a copied element's translation needs read the global
user's workspace. Left ambient, these are the approving editor's when the
resume runs in a backend request and whatever the worker holds there, so the
same call would copy and translate differently by where it runs — on 13.4, a
copy approved from live was taken back. The global user itself stays the
request's: its session is where an installation's hooks put their messages,
and the acting user, loaded for the run, has none. A process without a
global user keeps its refusal.

**They read the structure as that workspace shows it.** The record, the
source and target pages and an anchor are read with
`fetchRowAsTheUserSees()`; lists by parent — siblings, subpages, the
content on a page, a record's translations — with the predicate of
[ADR-263](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-263@main), each row under its live uid
(`ReadsTheRunsWorkspaceTrait::rowsShownIn()`,
`ReadsVersionsInTheWorkspaceTrait::translationsInWorkspace()`, which also
matches a translation pointing at the parent's version). A page or element
moved or new in the workspace is where the workspace puts it; another
workspace's draft is not there.

**The read-back reads the workspace**: the move version's page and column,
the copy and its translations new in the workspace and hidden, the
translation new there, and for a delete the record and its translations
gone from the workspace's view. A delete in a draft workspace does not ask
for the subpages and the content of a page, which core deletes live only
when the workspace is published.

**The card and the answer name the workspace.** Move, copy and translation
cards carry the line of [ADR-260](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-260@main); a delete card replaces
"Recoverable: yes, from the recycler" with "Workspace: … — marked for
deletion there; live stays unchanged until the workspace is published …
Until then the mark can be discarded in the Workspaces module". The answer
to the model ends with the draft note of [ADR-262](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-262@main); a delete
says "Marked … for deletion in the workspace …".

**Refused before the card** (`RefusesBeforeApprovalInterface`), in a
draft workspace, whatever the approver decides:

-   a run that did not record its workspace;
-   a table the call writes that core keeps no draft of
    (`refuseUnversionedTableInDraftWorkspace()`, [ADR-262](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-262@main));
    for a page copy, every table that holds records on the page — core copies
    them along, and a record of a table without versioning, such as
    `sys_template`, would be written live where the workspace allows live
    editing, and refused after the approval otherwise;
-   `delete_record` of a record new in the workspace, or moved there, and of
    a record whose translation is new or moved there — core's command would
    discard rather than delete (it deletes each translation the same way,
    `deleteL10nOverlayRecords()`); and of a page whose live branch holds a
    record — a subpage, a content element — the workspace moved away:
    publishing the mark runs core's live delete on the live branch, which
    deletes that record from where it still is and discards its move;
-   `create_translation_draft` with `overwrite` where a translation
    exists: refused on both cores, so the tool behaves the same on both and
    never leaves a marked translation without a new one;
-   on TYPO3 13.4, `create_translation_draft` where the translation in that
    language is marked for deletion in the workspace: 13.4 still counts it and
    refuses the localization (measured on 13.4.37); 14.3 makes the new one.

The refusal is asked after the plan's own checks, so a uid the user may not
touch keeps the neutral refusal, and
`PlansOneEditorialWriteTrait::refusalInItsWorkspace()` answers it for
all five. `execute()` and the card ask it first, through the one copy of
that opening in `AsksItsRefusalFirstTrait`.

**A call the card showed refused stays refused.** Beside a call that needs
approval, the turn waits as one, and an earlier call of it can remove the
reason before this one runs — it marks the translation an `overwrite`
would replace, or moves a record back into a branch to be deleted. Unlike
ADR-262's first refusals, which rest on configuration, these rest on rows.
So `ToolLoopService::resume()` asks every call's refusal once, after
the cards are compared and before any call runs, and answers those calls
with the refusal the approver read instead of running them.

**Another workspace's drafts still stop a delete.** In a draft workspace the
delete is only a mark; publishing it runs core's live delete, which discards
the drafts of every other workspace on what it deletes. The guard of
[ADR-198](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-198@main) keeps counting those, a translation this workspace
already marked included; the run's own workspace's drafts — the record's own
change there — are its own work and are not counted.

**Provenance.** Only `create_translation_draft` reports a `CREATED`
record; `MarkAiWrittenRecord` marks the translation new in the
workspace ([ADR-260](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-260@main)). Move, copy and delete report
`TEXT_UNCHANGED` or `DELETED` and are not marked, in live as before.

## Consequences {#consequences}

✓ An editor in a draft workspace can let the chat move, copy, delete and
translate pages and content; each change waits in the Workspaces module,
live stays as it was.

✓ A delete or an overwrite core would turn into something else in a
workspace is refused before the card, with the reason and what to do
instead.

✗ A record new in the workspace, or moved there, or one whose translation
is, cannot be deleted through the chat in that workspace; it is discarded in
the Workspaces module.

✗ A page that holds a TypoScript template, or any record of a table without
versioning, cannot be copied through the chat in a draft workspace.

✗ The drafts of other workspaces are counted when the delete is marked. A
draft another workspace makes afterwards is not: publishing the mark
discards it, as publishing the same mark made by hand in the page tree
would.

✗ Inline children are not asked about: a table core does not version that an
extension hangs below a content element as inline children would be copied
or localized live in a workspace that allows live editing. TYPO3's own
`tt_content` has none; its file references are versioned.

✗ `overwrite` of a translation is not available in a draft workspace,
although TYPO3 14.3 could do it.

✗ The limit [ADR-260](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-260@main) names for the card's read gate reaches
further here: the gate runs the plan in the viewer's own workspace. A member
of the run's workspace who views the card from another workspace gets it
withheld where the plan depends on the run's drafts — a record, page or
anchor new or moved there, and a delete whose record has a draft there
(counted as another workspace's from where the viewer sits). The gate fails
closed; the run's own user always sees the card.

✗ The approval card of a page delete counts the subpages and records the
workspace shows; publishing deletes the live branch, which a draft of the
same workspace may have changed.

✗ The first measurements of this decision ran with the aspect left at live,
as a worker has it, and recorded that a page new in the workspace belongs to
no site — so a copy left its elements' translations behind and a record on
such a page could not be translated. That described the process, not core;
with the aspect pinned both work as in live in either process. The field
writers of [ADR-260](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-260@main) and [ADR-262](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-262@main) and the
creating writers start their DataHandler with the aspect left ambient;
whether that changes what they write is not measured here.

✗ The global backend user stays the request's, so core reads its user
TSconfig and its page TSconfig conditions on the user inside the DataHandler
— the approving editor's in a backend request, not the run's user's. This
holds for every writer and predates this decision.

✗ Core caches a page's TSconfig for the rest of the process by page uid
alone (`BackendUtility::getPagesTSconfig()`, and the rootline under it),
whichever user asked. Where the page's TSconfig was read earlier in the same
process — by the approving request, or by another run in a worker — the
DataHandler gets that answer, not the run's user's; and the run's user's
answer is what later readers in the process get. The tools do not flush that
cache. It matters only where user TSconfig, a workspace version of page
TSconfig, or a page TSconfig condition on the backend user or the workspace
differs between the users, for the same page in the same process.
