{{ node.id }} {{ node.node_type }}
@if (!editing) {

{{ node.title }}

} @else { }
{{ node.priority }} {{ node.status }} @if (node.phase != null) { Phase {{ node.phase }} } @if (node.gitea_issue_number && node.gitea_repo) { #{{ node.gitea_issue_number }} }

Quality Gates

T M C S
@if (validationReport) { @if (validationReport.errors.length > 0) {
@for (err of validationReport.errors; track err) {
{{ err }}
}
} @if (validationReport.warnings.length > 0) {
@for (warn of validationReport.warnings; track warn) {
{{ warn }}
}
} @if (validationReport.valid && node.traceable && node.measurable && node.consistent && node.single_purpose) {
All quality gates passed
} @else if (validationReport.valid && validationReport.warnings.length === 0) {
No errors, but not all gates satisfied
} }

Description

@if (!editing) {

{{ node.description || 'No description' }}

} @else { }
@if (editing) {

Properties

}

Acceptance Criteria

@if (!editing) {
{{ node.acceptance_criteria || 'No acceptance criteria defined' }}
} @else { }

Test Specification

@if (!editing) {
{{ node.test_spec || 'No test specification defined' }}
} @else { }

Test Code

@if (!editing) {
{{ node.test_code || 'No test code' }}
} @else { }

Implementation Code

@if (!editing) {
{{ node.impl_code || 'No implementation code' }}
} @else { }
@if (!node.parent_id && node.node_type !== 'pillar') {
Orphaned — assign a parent to restore traceability
@if (reparentError) {
{{ reparentError }}
}
} @if (parentNode) {

Parent @if (editing) { }

@if (editing && showReparent) { @if (reparentError) {
{{ reparentError }}
} }
{{ parentNode.id }} {{ parentNode.priority }}
T M C S
{{ parentNode.title }}
{{ parentNode.status }}
}

Child Requirements ({{ children.length }})

@for (child of children; track child.id) {
{{ child.id }} {{ child.priority }}
T M C S
{{ child.title }}
@if (child.description) {
{{ child.description }}
} {{ child.status }}
}
@if (showAddChild) {

New Child Requirement

@if (createError) {
{{ createError }}
}
}

Also Satisfies ({{ outgoingLinks.length }})

@if (showAddLink) { } @else { }
@if (incomingLinks.length > 0) {

Satisfied By ({{ incomingLinks.length }})

}
Created {{ node.created_at | date:'short' }}
Updated {{ node.updated_at | date:'short' }}

History @if (historyList.length > 0) { ({{ historyList.length }}) } {{ showHistory ? '\u25BC' : '\u25B6' }}

@if (showHistory) {
@for (entry of historyList; track entry.id) {
v{{ entry.version }} {{ entry.created_at | date:'short' }}
{{ entry.change_summary || 'No summary' }}
} @empty {
No history yet
}
@if (selectedVersion) {

Version {{ selectedVersion.version }} Details

@for (field of [ {key: 'title', label: 'Title'}, {key: 'description', label: 'Description'}, {key: 'priority', label: 'Priority'}, {key: 'status', label: 'Status'}, {key: 'node_type', label: 'Node Type'}, {key: 'acceptance_criteria', label: 'Acceptance Criteria'}, {key: 'test_spec', label: 'Test Spec'}, {key: 'test_code', label: 'Test Code'}, {key: 'impl_code', label: 'Impl Code'} ]; track field.key) { @if (isFieldChanged(field.key)) {
{{ field.label }}
{{ getFieldOldValue(field.key) }}
{{ getFieldNewValue(field.key) }}
} }
} }
@if (!editing) { } @else { }
@if (showDeleteDialog) {
Delete "{{ node.title }}"

This node has {{ deleteChildren.length }} direct {{ deleteChildren.length === 1 ? 'child' : 'children' }}. Choose what happens to each:

@for (entry of deleteChildren; track entry.node.id) {
{{ entry.node.id }} {{ entry.node.title }} @if (entry.descendantCount > 0) { +{{ entry.descendantCount }} nested }
}
}