diff --git a/.vscode/launch.json b/.vscode/launch.json
index 94799b60..e77f8d67 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -23,6 +23,7 @@
"${workspaceFolder}/reporter/plaintext/src",
"${workspaceFolder}/reporter/html/src",
"${workspaceFolder}/reporter/aspec/src",
+ "${workspaceFolder}/reporter/ux/src",
"${workspaceFolder}/product/src/test/java",
"${workspaceFolder}/api/src",
"${workspaceFolder}/exporter/specobject/src",
diff --git a/README.md b/README.md
index 10af627d..3d62aef1 100644
--- a/README.md
+++ b/README.md
@@ -10,6 +10,8 @@ Below you see a screenshot of an HTML tracing report where OFT traces itself. Yo
+In addition to the HTML tracing report an interactive requirement browser and analysis tool is integrated into OpenFastTrace.
+
## Project Information
[](https://github.com/itsallcode/openfasttrace/actions/workflows/build.yml)
diff --git a/api/src/main/java/org/itsallcode/openfasttrace/api/ReportSettings.java b/api/src/main/java/org/itsallcode/openfasttrace/api/ReportSettings.java
index b8af61b7..494dd19c 100644
--- a/api/src/main/java/org/itsallcode/openfasttrace/api/ReportSettings.java
+++ b/api/src/main/java/org/itsallcode/openfasttrace/api/ReportSettings.java
@@ -19,7 +19,12 @@ public class ReportSettings
private final ColorScheme colorScheme;
private final DetailsSectionDisplay detailsSectionDisplay;
- private ReportSettings(final Builder builder)
+ /**
+ * Settings for a reporter.
+ *
+ * @param builder builder for a reporter
+ */
+ protected ReportSettings(final Builder builder)
{
this.verbosity = builder.verbosity;
this.showOrigin = builder.showOrigin;
@@ -121,7 +126,10 @@ public static class Builder
private ReportVerbosity verbosity = ReportVerbosity.FAILURE_DETAILS;
private ColorScheme colorScheme = ColorScheme.BLACK_AND_WHITE;
- private Builder()
+ /**
+ * Create the builder
+ */
+ protected Builder()
{
// empty by intention
}
diff --git a/core/src/main/resources/usage.txt b/core/src/main/resources/usage.txt
index f1616092..90c63c2c 100644
--- a/core/src/main/resources/usage.txt
+++ b/core/src/main/resources/usage.txt
@@ -8,7 +8,7 @@ Commands:
convert Convert to a different requirements format
Tracing options:
- -o, --output-format Report format, one of "plain", "html", "aspec"
+ -o, --output-format Report format, one of "plain", "html", "aspec", "ux"
Defaults to "plain"
-v, --report-verbosity Set how verbose the output is. Ranges from
"quiet" to "all".
diff --git a/doc/user_guide.md b/doc/user_guide.md
index 89afea1f..2635ec79 100644
--- a/doc/user_guide.md
+++ b/doc/user_guide.md
@@ -462,6 +462,18 @@ While plain text reports are perfect for debugging your tracing chain, sometimes
oft trace -o html
```
+### Interactive requirement analyisis
+
+Besides a basic HTML visualization of requirements OpenFastTrace also provides an interactive requirement browsing and requirement analysis frontend in the form of a responsive HTML page similar to the HTML report.
+
+The UX reporter:
+
+```
+oft trace -o ux
+```
+
+generates an input file for the OpenFastTrace-UX HTML frontend [OpenFastTrace-UX](https://github.com/poldi2015/openfasttrace-ux).
+
### Understanding and Fixing Broken Requirement Branches
Requirements — or specification items as we call them more broadly — in OFT are internally organized in a graph. If you haven't heard of that term, don't worry. In most cases it is close enough to think of the relationships between the specification items like a forest where the highest level of the specification are tree trunks from which details branch out into big branches, twigs and eventually leaves.
@@ -568,6 +580,7 @@ One of:
* `plain`
* `html`
* `aspec`
+* `ux`
Defaults to `plain`.
diff --git a/doc/ux_report_output.md b/doc/ux_report_output.md
new file mode 100644
index 00000000..565e9731
--- /dev/null
+++ b/doc/ux_report_output.md
@@ -0,0 +1,156 @@
+# UX reporter output format
+
+## Overview
+
+The UX reporter output format is the native output format of the openfasttrace ux-reporter.
+It is the input format for OpenFastTrace UX.
+
+## Data Structure
+
+The generated JavaScript object follows this structure:
+
+```javascript
+window.specitem = {
+ project: { /* project metadata */ },
+ specitems: [ /* array of specification items */ ]
+}
+```
+
+## Project Metadata
+
+The `project` object contains high-level information about the specification project:
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `projectName` | string | Name of the project |
+| `types` | string[] | Array of artifact types (e.g., "itest", "feat", "req", "arch", "utest") |
+| `tags` | string[] | Array of all tags used in the project |
+| `status` | string[] | Array of possible item statuses ("approved", "proposed", "draft", "rejected") |
+| `wrongLinkNames` | string[] | Array of wrong link type names ("version", "orphaned", "unwanted") |
+| `item_count` | number | Total number of specification items |
+| `item_covered` | number | Number of items that are covered |
+| `item_uncovered` | number | Number of items that are uncovered |
+| `type_count` | number[] | Count of items per type (indexed by type array) |
+| `uncovered_count` | number[] | Count of uncovered items per type |
+| `status_count` | number[] | Count of items per status |
+| `tag_count` | number[] | Count of items per tag |
+
+## Specitems Array Entries
+
+Each entry in the `specitems` array represents a single specification item with the following structure:
+
+### Basic Properties
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `index` | number | Unique sequential index of the item in the array |
+| `type` | number | Index into the `types` array indicating the item type |
+| `title` | string | Full title of the specification item |
+| `name` | string | Short name identifier of the item |
+| `id` | string | Full unique identifier in format "type:name[:version]" |
+| `tags` | number[] | Array of indices into the `tags` array |
+| `version` | number | Revision number of the specification item |
+| `status` | number | Index into the `status` array |
+
+### Content Properties
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `content` | string | Description/content of the specification item |
+| `comments` | string | Additional comments for the item |
+| `path` | string[] | File path components where the item is defined |
+| `sourceFile` | string | Source file path where the item is located |
+| `sourceLine` | number | Line number in the source file (0 if not available) |
+
+### Traceability Properties
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `provides` | number[] | Array of type indices that this item provides coverage for |
+| `needs` | number[] | Array of type indices that this item needs coverage from |
+| `covered` | number[] | Array of Coverage enum IDs per type (0=NONE, 1=UNCOVERED, 2=COVERED, 3=MISSING) |
+| `uncovered` | number[] | Array of type indices that are uncovered or missing for this item |
+| `covering` | number[] | Array of indices of items that this item covers |
+| `coveredBy` | number[] | Array of indices of items that cover this item |
+| `depends` | number[] | Array of indices of items that this item depends on |
+
+### Link Validation Properties
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `wrongLinkTypes` | number[] | Array of indices into `wrongLinkNames` for invalid link types |
+| `wrongLinkTargets` | string[] | Array of invalid link targets with format "target[reason]" |
+
+## Example Entry
+
+```javascript
+{
+ index: 0,
+ type: 2,
+ title: 'Title fea~fea1',
+ name: 'fea1',
+ id: 'fea:fea1',
+ tags: [],
+ version: 1,
+ content: 'Descriptive text for fea~fea1',
+ provides: [],
+ needs: [3],
+ covered: [0, 0, 2, 2, 1, 3],
+ uncovered: [4, 5],
+ covering: [],
+ coveredBy: [1, 2],
+ depends: [],
+ status: 0,
+ path: [],
+ sourceFile: '',
+ sourceLine: 0,
+ comments: '',
+ wrongLinkTypes: [],
+ wrongLinkTargets: []
+}
+```
+
+## Data Interpretation
+
+### Index-Based References
+
+Most numeric arrays in the specitems use indices to reference:
+- **Type indices**: Reference the `project.types` array
+- **Tag indices**: Reference the `project.tags` array
+- **Status indices**: Reference the `project.status` array
+- **Item indices**: Reference other items in the `specitems` array
+- **Wrong link type indices**: Reference the `project.wrongLinkNames` array
+
+### Coverage Arrays
+
+The `covered` array contains Coverage enum IDs per type, where each position corresponds to a specItem type in the `project.types` array. The Coverage enum values are:
+- **0 = NONE**: No coverage relationship exists for this type
+- **1 = UNCOVERED**: The specItem is not fully covered
+- **2 = COVERED**: The specItem is fully covered
+- **3 = MISSING**: These coverage type are needed by this specItems or specItems that cover this specItem (deep coverage) but are not covered
+
+For example, if `project.types = ["itest", "feat", "fea", "req", "arch", "utest"]` and `covered = [0, 0, 2, 2, 1, 3]`, this means:
+- itest: NONE (no coverage relationship)
+- feat: NONE (no coverage relationship)
+- fea: COVERED (coverage is complete)
+- req: COVERED (coverage is complete)
+- arch: UNCOVERED (coverage required but missing)
+- utest: MISSING (coverage exists but has issues)
+
+### Wrong Link Targets Format
+
+Wrong link targets are formatted as `"target[reason]"` where:
+- `target` is the invalid link target specification
+- `reason` explains why the link is invalid (e.g., "orphaned", "unwanted coverage", "outdated coverage")
+
+Example: `"itest:itest_wrong_type[unwanted coverage]"`
+
+## Usage Notes
+
+- All string values are properly escaped for JavaScript (quotes, HTML entities)
+- Long content strings are automatically wrapped across multiple lines with string concatenation
+- Empty arrays and strings indicate no data for that property
+- The data structure is designed for efficient lookup and filtering in web interfaces
+- Indices provide memory-efficient references while maintaining data integrity
+
+This data structure enables comprehensive traceability analysis and visualization in OpenFastTrace-UX applications.
diff --git a/oft-self-trace.sh b/oft-self-trace.sh
index 47b612c0..983d830a 100755
--- a/oft-self-trace.sh
+++ b/oft-self-trace.sh
@@ -27,6 +27,7 @@ if $oft_script trace \
"$base_dir/reporter/plaintext/src" \
"$base_dir/reporter/html/src" \
"$base_dir/reporter/aspec/src" \
+ "$base_dir/reporter/ux/src" \
"$base_dir/product/src/test/java" \
"$base_dir/api/src" \
"$base_dir/exporter/specobject/src" \
diff --git a/parent/pom.xml b/parent/pom.xml
index f1b712d0..e3a8fb3c 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -10,7 +10,7 @@
Free requirement tracking suitehttps://github.com/itsallcode/openfasttrace
- 4.1.0
+ 4.3.0175.11.43.5.2
@@ -186,6 +186,12 @@
${revision}compile
+
+ org.itsallcode.openfasttrace
+ openfasttrace-reporter-ux
+ ${revision}
+ compile
+ org.itsallcode.openfasttraceopenfasttrace-testutil
@@ -378,7 +384,7 @@
truetrue
- true
+ falsefalse-html5
diff --git a/pom.xml b/pom.xml
index 1d9da986..d7c43adc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -33,6 +33,7 @@
reporter/plaintextreporter/htmlreporter/aspec
+ reporter/uxtestutil
diff --git a/product/pom.xml b/product/pom.xml
index ced14315..6cc121cf 100644
--- a/product/pom.xml
+++ b/product/pom.xml
@@ -57,6 +57,10 @@
org.itsallcode.openfasttraceopenfasttrace-reporter-aspec
+
+ org.itsallcode.openfasttrace
+ openfasttrace-reporter-ux
+ org.itsallcode.openfasttraceopenfasttrace-testutil
diff --git a/product/src/test/java/org/itsallcode/openfasttrace/TestAllServicesAvailable.java b/product/src/test/java/org/itsallcode/openfasttrace/TestAllServicesAvailable.java
index 33368f05..c238dc79 100644
--- a/product/src/test/java/org/itsallcode/openfasttrace/TestAllServicesAvailable.java
+++ b/product/src/test/java/org/itsallcode/openfasttrace/TestAllServicesAvailable.java
@@ -100,7 +100,7 @@ void exporterAvailable(final String format)
@ParameterizedTest
@CsvSource(
- { "aspec", "html", "plain" })
+ { "aspec", "html", "plain", "ux" })
void reporterAvailable(final String format)
{
if (!reporterLoader.isFormatSupported(format))
diff --git a/product/src/test/java/org/itsallcode/openfasttrace/core/serviceloader/TestInitializingServiceLoader.java b/product/src/test/java/org/itsallcode/openfasttrace/core/serviceloader/TestInitializingServiceLoader.java
index 63f89839..8f91bffc 100644
--- a/product/src/test/java/org/itsallcode/openfasttrace/core/serviceloader/TestInitializingServiceLoader.java
+++ b/product/src/test/java/org/itsallcode/openfasttrace/core/serviceloader/TestInitializingServiceLoader.java
@@ -21,6 +21,7 @@
import org.itsallcode.openfasttrace.report.aspec.ASpecReporterFactory;
import org.itsallcode.openfasttrace.report.html.HtmlReporterFactory;
import org.itsallcode.openfasttrace.report.plaintext.PlaintextReporterFactory;
+import org.itsallcode.openfasttrace.report.ux.UxReporterFactory;
import org.junit.jupiter.api.Test;
/**
@@ -83,9 +84,10 @@ void testReporterFactoriesRegistered()
final ReporterContext context = new ReporterContext(null);
final List services = getRegisteredServices(ReporterFactory.class,
context);
- assertThat(services, hasSize(3));
+ assertThat(services, hasSize(4));
assertThat(services, containsInAnyOrder(instanceOf(PlaintextReporterFactory.class),
instanceOf(ASpecReporterFactory.class),
+ instanceOf(UxReporterFactory.class),
instanceOf(HtmlReporterFactory.class)));
for (final ReporterFactory factory : services)
{
diff --git a/reporter/ux/pom.xml b/reporter/ux/pom.xml
new file mode 100644
index 00000000..ae01cf9f
--- /dev/null
+++ b/reporter/ux/pom.xml
@@ -0,0 +1,32 @@
+
+ 4.0.0
+ openfasttrace-reporter-ux
+ OpenFastTrace UX Reporter
+
+ ../../parent/pom.xml
+ org.itsallcode.openfasttrace
+ openfasttrace-parent
+ ${revision}
+
+
+ ${reproducible.build.timestamp}
+
+
+
+ org.itsallcode.openfasttrace
+ openfasttrace-api
+
+
+ org.itsallcode.openfasttrace
+ openfasttrace-testutil
+ test
+
+
+ org.itsallcode.openfasttrace
+ openfasttrace-core
+ test
+
+
+
\ No newline at end of file
diff --git a/reporter/ux/src/main/java/module-info.java b/reporter/ux/src/main/java/module-info.java
new file mode 100644
index 00000000..7a3bb1a1
--- /dev/null
+++ b/reporter/ux/src/main/java/module-info.java
@@ -0,0 +1,17 @@
+/**
+ * This provides an interactive HTML requirement browser.
+ *
+ * @provides org.itsallcode.openfasttrace.api.report.ReporterFactory
+ */
+module org.itsallcode.openfasttrace.report.ux
+{
+ requires transitive org.itsallcode.openfasttrace.api;
+ requires java.logging;
+ requires java.desktop;
+ requires jdk.jfr;
+ requires java.xml.crypto;
+ requires java.xml;
+
+ provides org.itsallcode.openfasttrace.api.report.ReporterFactory
+ with org.itsallcode.openfasttrace.report.ux.UxReporterFactory;
+}
diff --git a/reporter/ux/src/main/java/org/itsallcode/openfasttrace/report/ux/Collector.java b/reporter/ux/src/main/java/org/itsallcode/openfasttrace/report/ux/Collector.java
new file mode 100644
index 00000000..8a6852ad
--- /dev/null
+++ b/reporter/ux/src/main/java/org/itsallcode/openfasttrace/report/ux/Collector.java
@@ -0,0 +1,647 @@
+package org.itsallcode.openfasttrace.report.ux;
+
+import org.itsallcode.openfasttrace.api.core.*;
+import org.itsallcode.openfasttrace.report.ux.model.Coverage;
+import org.itsallcode.openfasttrace.report.ux.model.UxModel;
+import org.itsallcode.openfasttrace.report.ux.model.UxSpecItem;
+import org.itsallcode.openfasttrace.report.ux.model.WrongLinkType;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.stream.Collectors;
+
+import static java.util.Map.entry;
+
+/**
+ * Collector traverses a {@link LinkedSpecificationItem} tree and provides a {@link UxSpecItem} and
+ * a {@link UxModel} based on the parsed items.
+ */
+public class Collector {
+
+ private final List items = new ArrayList<>();
+ private final List ids = new ArrayList<>();
+
+ private final List allTypes = new ArrayList<>();
+ private final List orderedTypes = new ArrayList<>();
+
+ private final List wrongLinkTypes = new ArrayList<>();
+
+ private final List tags = new ArrayList<>();
+ private final List tagCount = new ArrayList<>();
+
+ final List