Repository navigation
Modernize Copy Requirement Specification reqSpecCopy
opencode-agent edited this page Oct 2, 2026
·
1 revision
- Issue: #1797 (enhancement) — bugs found while testing: #1798, #1799, #1800
-
Legacy screen:
lib/requirements/reqSpecEdit.phpactionscopy/doCopy→gui/templates/dashio/requirements/reqSpecCopy.tpl -
Modern screen:
gui/templates/requirements/reqSpecCopy.html -
BFF:
api/reqspeccopy/index.php -
Entry point: a new Copy Requirement Specification button on the modern Requirement Specification Viewer (
gui/templates/requirements/reqSpecView.html) +$actions->reqSpecCopy
The TODO section of docs/MODERNIZATION-STATUS.md is empty (every ASIDE entry maps to a
modern screen), so the smallest coherent legacy-only slice was picked: the
requirement-specification copy popup, the last un-ported reqSpecEdit.php action.
Two things made it worth doing on its own:
-
The capability had been silently dropped in 2.0.1. The legacy
gui/templates/dashio/requirements/reqSpecView.tpl:52rendered a Copy Req. Spec button pointing atreqSpecEdit.php?doAction=copy. No modern screen replaced it, so after the modernization the modern Requirement Specification Viewer had no copy-specification entry point at all. -
The legacy write was unsafe.
reqSpecEdit.phprunscheckRights()against the SESSION context only, whilecontainerIDandtproject_idcame straight out of the request body — a copy could be aimed at any node of any test project. It also offered the source specification as its own destination, and itstarget_position(top / bottom) radio pair was dead UI:doCopy()calledcopy_to()with four arguments and never read the position.
| Legacy | Modern |
|---|---|
| Source card: doc id, title, test project, prefix, author, scope | same, scope rendered as escaped plain text (stripHtml) |
| Destination = the test project that owns the source | kept as the default, but a project switcher is offered |
Container <select> of the whole tree |
indented list, source subtree removed |
target_position radios (rendered, never used) |
actually honoured — node_order renumbered in two collision-free phases |
Message A copy of Req. Spec (DOCID:… - …) has been done (DOCID:… [n])
|
verbatim |
Copy / Cancel
|
Copy + Open the copy shortcut to the new specification |
-
mgt_view_reqandmgt_modify_reqon the destination project (the legacy screen only ever checked the session project). -
mgt_view_reqon the project that owns the source, so a specification cannot be read (or its metadata leaked) across projects. - The destination node is proved to be a test project / requirement specification of
the destination project (
rscResolveContainer), so a foreign container is a 400. - A destination inside the source subtree is refused
(
destination_inside_source) — legacy happily copied a spec into its own child. -
bffSameOriginGuard()+bffEnforceSession()on the write, stable machine codes on every error answer (400/401/403/404/405). - The legacy
copy/doCopyURLs are now a non-mutating 302 shim placed beforenew reqSpecCommands()/initialize_gui(), so the old write is not executed and the request does not even reach the GUI initializers. It carries?legacy_intent=, and the popup then says "Nothing has been copied yet".
GET ?action=init&req_spec_id=N[&tproject_id=P]
→ source, destination, default_project_id, destinations[], rights, tproject_fallback_from
GET ?action=projects → the test projects the caller may write into
POST ?action=copy&req_spec_id=N&container_id=M[&tproject_id=P]&target_position=top|bottom
→ new{id,doc_id,title}, position
| Issue | What |
|---|---|
| #1798 |
?action=projects listed orphan node_type_id=1 nodes (a nodes_hierarchy row with no testprojects row, which testproject::delete() leaves behind) as destinations. rscResolveContainer proves the destination against testprojects, so every pick of one died with 400. Now an INNER JOIN testprojects … AND TP.active = 1. |
| #1799 | Five screen defects: (1) the success box was wiped by the post-copy load(), so a successful copy looked like a no-op; (2) showState() picked "the first <p> without a <code>", which is the access-denied hint — every error card showed the wrong explanation and an empty machine code; (3) the first repair of (2) lower-cased notFound while the markup id is notFoundCode, so it silently did nothing; (4) a stale ?tproject_id= (exactly what the legacy shim hands over — the SESSION id) produced zero containers, an empty selector and a disabled Copy; (5) the ?legacy_intent= notice was wiped twice and threw ReferenceError: p is not defined. |
| #1800 | Event Viewer: E_WARNING Undefined property: tlUser::$id on every denial (tlUser has no ->id; the id is the protected dbID of tlObject), and one Trying to access array offset on null pair per shim hit because the redirect ran after initialize_gui(). |
Suite 1797 = 25 / 25 PASS (tmp/TLU_Test_Cases.md), covering the two positions,
the project root, a cross-project copy, the empty-destination state, the 400/401/403/404/405
matrix, both legacy shim branches, the toolbar button, the "every other reqSpecEdit.php
action still works" regression, and the Romanian bundle (0 missing data-i18n keys).
Event Viewer clean — three consecutive shim hits add 0 rows.
-
— the screen -
— after a successful copy -
— no destination available -
— the 404 card + machine code -
— the ?legacy_intent=notice -
— Romanian