Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
# Project-local SSH configuration (keys, config, known_hosts)
.ssh/

# Local agent config files/folders
.opencode/
opencode.json

# Any local run configuration file
.run

# Local git worktrees
.worktrees/

Expand Down
80 changes: 63 additions & 17 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ PRD (docs/prd.md)
└── Implementation (code, PRs)
```

Significant architectural decisions are recorded as **Architecture Decision Records (ADRs)** in
[`docs/adr/`](docs/adr/README.md), which provide rationale and context that span across the
traceability chain above.

Stories are tracked in two places: `docs/features.md` (the stakeholder-facing, stable record) and
GitHub issues (the implementation tracking record). When a story moves from draft (discussion in
SharePoint Word doc) to approved (ready for implementation), it receives a stable Story ID and is
Expand Down Expand Up @@ -238,21 +242,45 @@ Rules:

#### Story Lifecycle

Stories move through a lifecycle from draft to implementation. This flow keeps the GitHub issue history clean and ensures stakeholders have a stable, reviewable document.

1. **Draft (Discussion)** — The story is captured in a SharePoint Word document or similar internal medium where discussion, refinement, and iteration happen. No stable ID is assigned. The story is not yet tracked in `docs/features.md`.
2. **Approved (Ready for Implementation)** — The story is finalised, a stable `FEAT-<SLUG>-<NN>` ID is assigned, and it is:
- Written into `docs/features.md` with full narrative and acceptance criteria
- A GitHub issue is created (if it does not already exist) or an existing issue is updated with the stable ID in the title and body
- The status in `docs/features.md` is updated from 🔴 (Open) to 🟡 (In Progress) when implementation begins, and 🟢 (Done) when complete
3. **Implementation** — Tasks are created in GitHub referencing the stable story ID (not the GitHub issue number). Implementation traces: Task → Story (stable ID) → Feature → Requirement → PRD.
Stories move through a lifecycle from draft to implementation. This flow keeps the GitHub issue
history clean and ensures stakeholders have a stable, reviewable document.

The lifecycle spans two **decoupled surfaces**, with the Product Owner as the handoff actor:

- **Stakeholder space** (Word Online / SharePoint, email) — where stakeholders and the PO
draft and discuss stories. This material is PO-internal; developers do not read it.
- **Developer space** (GitHub issues, `docs/features.md`) — where approved stories become
versioned, traceable specification that drives Tasks and implementation.

The handoff between these two spaces is a deliberate, reviewable action performed by the PO:

1. **Draft (Discussion)** — The story is drafted in stakeholder space (Word Online /
SharePoint) where the PO and stakeholders discuss, refine, and iterate. No stable ID is
assigned. The story is not yet tracked in GitHub or `docs/features.md`.
2. **Approved (Ready for Implementation)** — The PO finalises the story, assigns a stable
`FEAT-<SLUG>-<NN>` ID, and performs the handoff:
- A Feature issue is created (if one does not already exist).
- A Story issue is created using `.github/ISSUE_TEMPLATE/story.yml`, with the stable ID in
the title and body, the parent Feature linked, and at least one `R-<NN>` referenced.
- The story is written into `docs/features.md` with full narrative and acceptance criteria.
- The status in `docs/features.md` starts at 🔴 (Open) and moves to 🟡 (In Progress) when
implementation begins, then 🟢 (Done) when complete.
3. **Implementation** — Tasks are created in GitHub referencing the stable story ID (not the
GitHub issue number). Implementation traces:
Task → Story (stable ID) → Feature → Requirement → PRD.

**Rules:**

- A story must never be implemented without a stable ID in `docs/features.md`.
- When a story was created on GitHub before this flow existed (e.g., old issue numbers), update the existing issue with the stable ID rather than creating a new one.
- Tasks reference stories by their stable ID in the "Parent Story" field, not by GitHub issue number.
- The GitHub issue remains the source of truth for implementation tracking (comments, sub-issues, assignees), but `docs/features.md` is the source of truth for story content (narrative, acceptance criteria).
- When a story was created on GitHub before this flow existed (e.g., old issue numbers),
update the existing issue with the stable ID rather than creating a new one.
- Tasks reference stories by their stable ID in the "Parent Story" field, not by GitHub issue
number.
- The GitHub issue remains the source of truth for implementation tracking (comments,
sub-issues, assignees), but `docs/features.md` is the source of truth for story content
(narrative, acceptance criteria).
- Post-approval changes to a story happen via a PR to `docs/features.md` and a corresponding
GitHub issue comment — not by editing the original Word Online draft.

---

Expand Down Expand Up @@ -665,7 +693,11 @@ When working on this codebase, an AI agent should:
`finances`, or cross-cutting).
2. **Identify the layer** (`domain`, `application`, `infrastructure`, `views`/UI).
3. Check `ExceptionHandling.md` and `service_api.md` for patterns relevant to the change.
4. Read existing tests in the same module before writing new code.
4. **Check `docs/adr/README.md`** for existing ADRs that apply to the change area. If the change
is a significant architectural decision, consider creating a new ADR using the MADR template
at `docs/adr/templates/madr-template.md`. See [`docs/adr/README.md`](docs/adr/README.md) for
naming conventions and rules.
5. Read existing tests in the same module before writing new code.

### Creating Features

Expand All @@ -677,11 +709,21 @@ When working on this codebase, an AI agent should:

### Creating Stories

- Stories start as drafts (discussion/refinement in SharePoint Word or similar internal medium). They are NOT tracked in `docs/features.md` or GitHub while in draft.
- When a story is finalised and approved for implementation: assign a stable `FEAT-<SLUG>-<NN>` ID, write it into `docs/features.md` with full narrative and acceptance criteria, and create or update the corresponding GitHub issue with the stable ID in the title and body.
- For stories that were created on GitHub before this workflow existed, update the existing issue with the stable ID rather than creating a new one.
- Tasks reference the story by its stable ID (e.g., `FEAT-IP-MEAS-01`), not by GitHub issue number.
- If you are unsure whether a new Story is needed or an existing Feature should be extended, pause and ask a human reviewer.
- Stories start as drafts in stakeholder space (Word Online / SharePoint, email) — PO-internal
material that developers do not read. They are NOT tracked in `docs/features.md` or GitHub
while in draft.
- When a story is ready for handoff: the PO translates it into GitHub, using
`.github/ISSUE_TEMPLATE/story.yml`, assigns a stable `FEAT-<SLUG>-<NN>` ID, writes it into
`docs/features.md` with full narrative and acceptance criteria, and links it to the parent
Feature issue.
- For stories that were created on GitHub before this workflow existed, update the existing
issue with the stable ID rather than creating a new one.
- Tasks reference the story by their stable ID (e.g., `FEAT-IP-MEAS-01`), not by GitHub
issue number.
- Post-approval changes happen via PR against `docs/features.md` — not by editing the Word
Online draft.
- If you are unsure whether a new Story is needed or an existing Feature should be extended,
pause and ask a human reviewer.

### Making domain changes

Expand Down Expand Up @@ -732,6 +774,8 @@ An agent should pause and request human review/approval before:
- Implementing a change that introduces new system capability not covered by an existing requirement.
- Retiring or renaming a Feature slug once Stories reference it.
- Splitting a Feature into multiple Features when Stories already reference the original Feature.
- Creating a new Architecture Decision Record (ADR) — to ensure the decision is captured in the
correct format and the ADR index in `docs/adr/README.md` is updated.

---

Expand All @@ -742,6 +786,8 @@ An agent should pause and request human review/approval before:
| `docs/requirements.md` | Authoritative requirement registry — all R/NFR/C requirements documented here; must be updated before new capabilities are implemented |
| `docs/requirements-guide.md` | Authoring conventions for creating, editing, and retiring requirements |
| `docs/features.md` | Stakeholder-facing features and user stories tracker — stable story records with narrative, acceptance criteria, and status; stories move here from draft once approved |
| `docs/adr/README.md` | Architecture Decision Records index — naming rules, creation process, and list of ADRs |
| `docs/adr/templates/madr-template.md` | MADR template for creating new ADRs (see [`docs/adr/README.md`](docs/adr/README.md)) |
| `README.md` | Setup, configuration reference, how to run |
| `ExceptionHandling.md` | Exception handling conventions (read before touching error handling) |
| `service_api.md` | Service API design patterns (Mono/Flux, request/response shapes) |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ public enum DateTimeFormat {
* Format: {@code <weekday_name>, <day> <month_name> <year>}}
*/
SIMPLE_DATE,

/**
* A more compact date representation. E.g. {@code 11 Feb 2026}
* Format: {@code <day> <month_name> <year>}
*/
SIMPLE_DATE_SHORT,

/**
* A simple to read, textual date-time representation. E.g.
* {@code Wednesday, 11 February 2026 11:12:10}
Expand Down Expand Up @@ -78,6 +85,7 @@ public static DateTimeFormatter asJavaFormatter(@NonNull DateTimeFormat format,
case ISO_LOCAL_DATE_TIME_WHITESPACE_SEPARATED -> DateTimeFormatter.ofPattern(
"yyyy-MM-dd HH:mm").withZone(zoneId);
case SIMPLE_DATE -> DateTimeFormatter.ofPattern("EEEE, dd LLLL yyyy").withZone(zoneId);
case SIMPLE_DATE_SHORT -> DateTimeFormatter.ofPattern("d MMM yyyy").withZone(zoneId);
case SIMPLE_DATE_TIME -> DateTimeFormatter.ofPattern("EEEE, dd LLLL yyyy HH:mm:ss").withZone(
zoneId);
};
Expand All @@ -95,6 +103,7 @@ public static DateTimeFormatter asJavaFormatter(@NonNull DateTimeFormat format,
public static String asMariaDbDatabasePattern(@NonNull DateTimeFormat format) {
return switch (format) {
case SIMPLE_DATE -> "%W, %d %M %Y";
case SIMPLE_DATE_SHORT -> "%d %M %Y";
case SIMPLE_DATE_TIME -> "%W, %d %M %Y %T";
case ISO_LOCAL_DATE -> "%Y-%m-%d";
case ISO_LOCAL_DATE_TIME -> "%Y-%m-%dT%T";
Expand Down
122 changes: 122 additions & 0 deletions datamanager-app/front-end-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,5 +136,127 @@ classDiagram

```

## Alert dialog

```mermaid
classDiagram
note for Component "Vaadin Component"
AlertDialog <-- AppDialog
AppDialog --> DialogHeader
AppDialog --> DialogBody
AppDialog --> DialogFooter
AppDialog --|> Dialog

class AlertDialog {
<<builder>>
+alert(Component parent) Builder
+danger(Component parent, String title, String message, DialogAction onConfirm) AlertDialog
+open()
+close()
+dialog() AppDialog
}

class Builder {
+intent(Intent intent) Builder
+danger() Builder
+warning() Builder
+error() Builder
+info() Builder
+title(String title) Builder
+message(String message) Builder
+confirmButton(String label, DialogAction action) Builder
+cancelButton(String label, DialogAction action) Builder
+build() AlertDialog
}

class AppDialog {
+small() AppDialog
+registerConfirmAction(DialogAction action)
+registerCancelAction(DialogAction action)
+open()
+close()
}

class DialogHeader {
+withIcon(AppDialog dialog, String title, Icon icon)
}

class DialogBody {
+withoutUserInput(AppDialog dialog, Component component)
}

class DialogFooter {
+withDangerousConfirm(AppDialog dialog, String cancelText, String confirmText)
+with(AppDialog dialog, String cancelText, String confirmText)
+withConfirmOnly(AppDialog dialog, String confirmText)
}

class DialogAction {
<<interface>>
+execute()
}
```

### When to use

| Need | Pattern |
|---|---|
| Destructive action confirmation | `AlertDialog.danger(parent, title, message, onConfirm).open()` |
| Error requiring acknowledgment | `AlertDialog.alert(parent).error().title(...).message(...).confirmButton("OK", () -> {}).build().open()` |
| Error with redirect + cancel | `AlertDialog.alert(parent).error().confirmButton("Go...", nav).cancelButton("Cancel", close).build().open()` |
| Cancel / discard changes | `CancelConfirmationDialogFactory.cancelConfirmationDialog(action, key, locale).open()` |
| Success / info / transient feedback | `MessageSourceNotificationFactory.toast(...)` (unchanged) |
| Pending task with progress bar | `MessageSourceNotificationFactory.pendingTaskToast(...)` (unchanged) |

### Code examples

**Destructive confirmation (most common):**
```java
AlertDialog.danger(this,
"Samples within batch will be deleted",
"Deleting this Batch will also delete the samples contained within. Proceed?",
() -> deleteBatch(batchId)).open();
```

**Error dialog with single button:**
```java
AlertDialog.alert(this)
.error()
.title("Cannot edit variables")
.message("Editing experimental variables is only possible if samples are not registered.")
.confirmButton("Okay", () -> {})
.build()
.open();
```

**Warning with cancel:**
```java
AlertDialog.alert(this)
.warning()
.title("Discard changes?")
.message("By aborting, you will lose all entered information.")
.confirmButton("Discard", () -> discard())
.cancelButton("Continue Editing", () -> {}) // close dialog
.build()
.open();
```

### Deprecated: NotificationDialog

`NotificationDialog` has been removed and replaced with `AlertDialog`. The following classes were deleted:

- `AccessTokenDeletionConfirmationNotification`
- `BatchDeletionConfirmationNotification`
- `MeasurementDeletionConfirmationNotification`
- `QCItemDeletionConfirmationNotification`
- `PurchaseItemDeletionConfirmationNotification`
- `ProjectUserRemovalConfirmationNotification`
- `ExistingGroupsPreventVariableEdit`
- `ExistingSamplesPreventVariableEdit`
- `ExistingSamplesPreventSampleOriginEdit`
- `ExistingSamplesPreventGroupEdit`

If any code still references `NotificationDialog`, it will throw `UnsupportedOperationException` at runtime. Use the patterns above instead.



43 changes: 43 additions & 0 deletions datamanager-app/frontend/themes/datamanager/components/all.css
Original file line number Diff line number Diff line change
Expand Up @@ -1454,6 +1454,10 @@ Older stuff -
flex-grow: 1;
}

.flex-shrink-0 {
flex-shrink: 0;
}

.wrapping-flex-container {
flex-wrap: wrap;
}
Expand Down Expand Up @@ -1497,3 +1501,42 @@ Older stuff -
.overflow-auto {
overflow: auto;
}

/*region Multi-line text truncation (line clamp)*/
/*
Truncate text after N visible lines. Uses the WebKit-specific
-webkit-line-clamp property, which requires -webkit-box +
-webkit-box-orient:vertical to function. Works in all browsers
that support Chromium (including Vaadin). The "line-height:1.4"
keeps truncated text visually consistent with surrounding labels.
*/
.clamp-1-line,
.clamp-2-line,
.clamp-3-line {
display: -webkit-box;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1.4;
white-space: normal;
}

.clamp-1-line { -webkit-line-clamp: 1; }
.clamp-2-line { -webkit-line-clamp: 2; }
.clamp-3-line { -webkit-line-clamp: 3; }
/*endregion*/

/*region Positioned centre-fill overlay*/
/*
Absolutely fills and centres content within its nearest
positioned ancestor. Used for loading spinners, welcome/empty
states, and modal-like overlays that sit inside a flex container.
*/
.overlay-center-fill {
position: absolute;
inset: 0; /* top:0; right:0; bottom:0; left:0 */
display: flex;
align-items: center;
justify-content: center;
}
/*endregion*/
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
.analysis-type-combo-box::part(overlay) {
width: 20rem;
}

/*
* The "Connect dataset" sidebar prototype uses a ComboBox inside a fixed-position
* sidebar panel at z-index 1000. Vaadin's base overlay uses a hard-coded z-index of
* 200. This rule, applied via setOverlayClassName(), raises the overlay above the
* sidebar so the dropdown is actually selectable.
*/
vaadin-combo-box-overlay.connect-dataset-sidebar-overlay {
z-index: 1001 !important;
}
Loading
Loading