Skip to content

Requirement Spec Revision Compare

opencode agent edited this page Sep 19, 2026 · 6 revisions

Requirement Spec Revision Compare — Modernized Screen

Modernization of Requirement Spec Revision Compare (lib/requirements/reqSpecCompareRevisions.php) — issue #837. Closes the HIGH-priority reqSpecViewRevisions (revision compare) item of #755.

The legacy Smarty diff screen is replaced by a standalone Dashio page (gui/templates/requirements/reqSpecCompare.html) backed by the requirement-spec BFF (api/reqspec/index.php, new spec_revision_compare action). It is a non-ASIDE screen reached from the modernized Requirement Specification Viewer (reqSpecView.html) and Spec Revision Viewer (reqSpecViewRevision.html) via the new "Compare revisions" button.

URL: gui/templates/requirements/reqSpecCompare.html?spec_id=<spec_node_id>&tproject_id=<id> BFF API: api/reqspec/index.php?action=spec_revision_compare Rights: mgt_view_req (view) enforced server-side (same gate as spec_revision_view).

Selection state

Diff state


Table of Contents

  1. What the screen does
  2. REST API Reference
  3. Legacy parity notes
  4. i18n Keys
  5. Security
  6. Testing

1. What the screen does

Feature Legacy behavior Modern implementation
Revision picker table of revisions (revision, left/right radios, log message, timestamp) DataTable of revisions (ordered DESC) with two radio columns (left / right), log message, "last change" timestamp + editor; most recent two pre-selected
Diff method radio htmlCompare (DaisyDiff) vs htmlCodeCompare (text diff/inline + context + show-all) same two radio options; context input + "Show all" shown only in code-compare mode
Attributes diff getAttrDiff() table (doc_id, name, type) with changed highlighting same — attribute table with changed rows highlighted
Scope diff diff/HTMLDiffer on the scope of each revision same — HTML ins/del or text diff table; "No changes" when equal
Custom fields diff getCFDiff() table of linked CF values on each revision same — CF table, show_custom_fields_without_value honored
Validation two distinct revisions required, context must be numeric ≥ 0 same client-side validation before the compare request
Context default (gap #1360) Context input prefilled with diffEngine default 5 so code-compare runs without typing (lib/requirements/reqSpecCompareRevisions.php:241-245; tpl line 251 renders value={$gui->context}) same — HTML code-comparison mode shows Context prefilled with 5; the BFF list response returns context (mirror of sibling reqCompare.html:153-154 / api/reqcompare/index.php:161); user can still edit it or tick "Show all"
Revision ordering (gap #1362) newest-first (DESC by revision number) same — DataTable disabled default client-side ascending sort; the Revision cell carries an integer data-order sort key and the DataTable is initialised with order: [[0,"desc"]], so rows render newest-first AND sort numerically for 10+ revisions ("Revision 10" > "Revision 2")
Cancel/Back (gap #1359) two Cancel buttons (cancel_top tpl line 209 + cancel_bottom line 258, both btn_cancel → history.back()) so the user can leave the compare page without the browser Back same — a btn-ghost Cancel button (.fa-arrow-left + common.cancel) in the toolbar and a second one in the bottom footer; both call goBack() = history.back(), with a same-origin fallback to reqSpecView.html?id=<spec>&tproject_id=<tid> when the page was opened directly (history.length <= 1)
Revision-popup link (gap #1358) the Last change cell is clickable (blue rgb(0,85,153), hand cursor) and opens the spec-revision viewer popup for that row (reqSpecCompareRevisions.tpl:234 → openReqSpecRevisionWindow(item_id) → lib/requirements/reqSpecViewRevision.php sized from ReqPopupWidth/ReqPopupHeight cookies, window name ReqSpecRevisionView) same — the timestamp cell is a clickable link (cursor:pointer;color:rgb(0,85,153) + tooltip rsvc.openRevision) calling openReqSpecRevision(item_id), which opens the modern reqSpecViewRevision.html?revision_id=<id> viewer popup, cookie-sized (defaults 800x600), window name ReqSpecRevisionView
Log-message hover tooltip (gap #1357) every revision's Log message cell (id="tooltip-<item_id>", tpl:231-233) opens an Ext.ToolTip (tip4log, tpl:32-48 — width 500, dismissDelay 0, trackMouse) that autoLoads the FULL untruncated log from lib/ajax/getreqspeclog.php?item_id=; the cell itself shows the log truncated to req_spec_cfg->log_message_len (200, controller reqSpecCompareRevisions.php:262-271); an empty log makes getreqspeclog.php return the empty_log_message placeholder same — the BFF list response returns log_message_len (from req_spec_cfg), the cell renders the log truncated to that length + ... exactly like legacy, and a mouse-tracked #logTooltip div (legacy-affordance: width 500 / dismissDelay 0 / trackMouse, event-delegated on #revTable so it survives DataTables rebuilds) shows the FULL untruncated log with the getreqspeclog.php <p>→newline normalization; empty logs render the translated common.emptyLogMessage placeholder instead of the old '-' fallback

Log-message hover tooltip with the full log

Descending revision order

Context prefilled with default 5 in code-compare mode

Code-compare diff rendered with prefilled context

Selection state with the Cancel button in toolbar

2. REST API Reference

All routes are session-authenticated and JSON; CSRF Origin header required.

Method Route Query Returns
GET ?action=spec_revision_compare spec_id, tproject_id {status, tproject_id, tproject_name, spec_id, spec_doc_id, context, log_message_len, revisions:[{item_id, revision, log_message, timestamp, last_editor}]}
GET ?action=spec_revision_compare spec_id, tproject_id, left, right, method=html|text, context, context_show_all {status, left, right, method, attributes:[{label,lvalue,rvalue,changed}], scope:{type,left,right,count,diff}, custom_fields:[{label,lvalue,rvalue,changed}]}

Error conditions

  • Missing/invalid session → HTTP 401.
  • Unknown spec → 404 Requirement specification not found.
  • Test project mismatch → 400.
  • No mgt_view_req right on the owning test project → 403.
  • left/right missing or equal → 400 Select two different revisions to compare.
  • One/both revisions not found → 404.

3. Legacy parity notes

  • The revision list comes from requirement_spec_mgr->get_history() (same as legacy), returning item_id/revision/log_message/timestamp/last_editor.
  • The diff reads the full req_specs_revisions row (doc_id/name/type/scope) and linked design custom fields for each revision (1.x design-node sink), same as the modernized spec_revision_view action.
  • Two diff engines are ported 1:1: HTMLDiffer::htmlDiff (HTML comparison) and diff::doDiff + inline (HTML code comparison with a context window).
  • The scope is compared for both engines; attributes and custom fields use the legacy changed flag for row highlighting.
  • Context default (gap #1360): the BFF returns the diffEngine->context default (config.inc.php:1774, = 5) in the revision-list response, and also uses it server-side as fallback when a text-compare request omits context (previously the fallback was null). The screen prefills the Context input from that field (r.context || 5), identical to legacy and to the sibling requirement-version compare screen.
  • Cancel/Back (gap #1359): legacy reqSpecCompareRevisions.tpl exposes cancel_top (line 209) and cancel_bottom (line 258) — two btn_cancel buttons calling history.back(). The modern screen restores the same affordance in the toolbar and in the bottom footer (label common.cancel, no new i18n keys); goBack() keeps exact legacy semantics on a flowing history and adds a same-origin deep-link fallback to reqSpecView.html (pattern mirrors reqEdit.html:58 + reqEdit.html:219).
  • Revision-popup link (gap #1358): legacy reqSpecCompareRevisions.tpl:234 renders the Last change cell as a link calling openReqSpecRevisionWindow({$rspec.item_id}) (gui/javascript/testlink_library.js:1743), which opens lib/requirements/reqSpecViewRevision.php?showReqSpecTitle=1&item_id=N in a popup sized from the ReqPopupWidth/ReqPopupHeight cookies (defaults 800x600), window name ReqSpecRevisionView. The modern screen restores the same affordance: the timestamp cell is clickable (cursor:pointer, legacy blue rgb(0,85,153), tooltip rsvc.openRevision) and calls the new openReqSpecRevision(item_id) helper, which opens the modern revision viewer gui/templates/requirements/reqSpecViewRevision.html?revision_id=<id> with the same cookie sizing/window name. item_id is the same req_specs_revisions.id ref the BFF already returns per row, so no BFF change was needed.
  • Log-message hover tooltip (gap #1357): legacy cells were truncated server-side to req_spec_cfg->log_message_len (200) with ... and the full message was loaded on hover through lib/ajax/getreqspeclog.php (nl2br, strips surrounding <p>, falls back to empty_log_message). The modern screen keeps the FULL log_message in the BFF payload (no extra fetch round-trip) and mirrors the rest 1:1: the BFF list response now exposes log_message_len (config_get('req_spec_cfg')->log_message_len), the cell truncates to it with ..., a mouse-tracked #logTooltip (Ext.ToolTip affordance: width 500, dismissDelay 0, trackMouse) shows the full log normalizing <p> → newline, and blank logs render the translated common.emptyLogMessage placeholder.

4. i18n Keys

All labels are client-side via TLi18n; keys under the rsvc. namespace (rsvc.title, rsvc.selectRevisions, rsvc.selectTwo, rsvc.sameSelected, rsvc.noSpec, rsvc.noRevisions, rsvc.loadError, rsvc.diffMethod, rsvc.htmlCompare, rsvc.codeCompare, rsvc.context, rsvc.showAll, rsvc.revision, rsvc.compare, rsvc.logMessage, rsvc.timestamp, rsvc.left, rsvc.right, rsvc.compareSelected, rsvc.invalidContext, rsvc.computing, rsvc.diffDetails, rsvc.diffBetween, rsvc.attributes, rsvc.attribute, rsvc.scope, rsvc.noChanges, rsvc.changes, rsvc.customFields, rsvc.customField, rsvc.openRevision). The link labels use rsv.compareRevisions / rsvr.compareRevisions. Present in all 10 bundles (en ro de es fr it ja pt ru zh). The log-tooltip placeholder adds the shared common.emptyLogMessage key ("Log message is empty", legacy empty_log_message) in all 10 bundles.

5. Security

  • Server-side rights check on every route: mgt_view_req required even to list revisions; the spec's owning test project is resolved from the req_specs row.
  • CSRF guarded via Origin header check (all BFF routes).
  • Output is HTML-escaped client-side; DB reads use parameterized queries / int-casts on every id.

6. Testing

See Suite 837 — Requirement Spec Revision Compare in tmp/TLU_Test_Cases.md (13/13 PASS): revision list, HTML diff, code-compare diff, same-revision validation, empty-context validation, missing-spec error, BFF 404/400 guards, link switches from both the current-spec viewer and the revision viewer, locale switch, console cleanliness, and Event Viewer cleanliness.

See also Suite 1360 — Context default prefill (8/8 PASS): the code-compare Context input is prefilled with 5 from the BFF context field on load, compare runs without typing, "Show all" still disables the field, HTML-compare (Daisy) path unaffected, explicit context override honored, BFF fallback default, syntax gates, and Event Viewer/console cleanliness.

See also Suite 1359 — Cancel/Back buttons (10/10 PASS): toolbar + footer Cancel buttons present, both return to the calling screen via history.back(), direct-open fallback navigates to the spec viewer, compare/diff regression passes, common.cancel reused from all 10 locale bundles, Event Viewer and console clean. Screenshots: issue-1359-gap-before.png, issue-1359-cancel-buttons.png.

See also Suite 1358 — Revision-popup link on the Last-change cell (10/10 PASS): each row's timestamp cell is clickable (blue + pointer cursor) and opens reqSpecViewRevision.html?revision_id=<item_id> for that exact revision (cookie-sized popup, window name ReqSpecRevisionView), popup content matches the selected revision, compare/diff regression passes, rsvc.openRevision present in all 10 locale bundles, Event Viewer + console + PHP log clean.

Clickable Last-change cells

Revision-viewer popup opened from the timestamp cell

See also Suite 1357 — Log-message hover tooltip (10/10 PASS): the Log message cell truncates to req_spec_cfg->log_message_len (200) + ... like legacy, hovering shows the FULL untruncated log in a mouse-tracked tooltip (survives DataTables refresh), empty logs render the translated common.emptyLogMessage placeholder instead of '-', the BFF list response exposes log_message_len, compare/diff regression passes, i18n gate 10/10 bundles, Event Viewer + console clean. Screenshot: images/issue-1357-tooltip.png.

Clone this wiki locally