Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46279f740b |
@@ -55,7 +55,6 @@ Conflict-prevention rules:
|
|||||||
|
|
||||||
- `work/platform` owns `src/main/resources/db/migration/**`, Maven/dependency configuration, Compose, container build files, and CI workflow files. Other branches request schema changes instead of independently allocating migration versions.
|
- `work/platform` owns `src/main/resources/db/migration/**`, Maven/dependency configuration, Compose, container build files, and CI workflow files. Other branches request schema changes instead of independently allocating migration versions.
|
||||||
- `work/reports-ui` owns shared templates/fragments, shared design tokens, and general UI assets. Each domain branch owns its module-specific controllers and pages while consuming those shared fragments.
|
- `work/reports-ui` owns shared templates/fragments, shared design tokens, and general UI assets. Each domain branch owns its module-specific controllers and pages while consuming those shared fragments.
|
||||||
- A targeted repair shall use a clean, isolated `work/fix/<feature>/<what-fix>` branch and worktree from the taskmaster-verified current `main`. Do not use `work/<feature>/fix/<what-fix>`: the persistent `work/<feature>` ref already occupies that Git ref prefix.
|
|
||||||
- `work/tasks` exposes focused Task query/transfer operations required by Project workflows. `work/projects` owns the transaction that removes a member or completes a Project.
|
- `work/tasks` exposes focused Task query/transfer operations required by Project workflows. `work/projects` owns the transaction that removes a member or completes a Project.
|
||||||
- `work/platform` owns HolidayAPI credential storage and the tested HTTP client. `work/attendance` owns preview interpretation, selection, deduplication, import, and day-off effects.
|
- `work/platform` owns HolidayAPI credential storage and the tested HTTP client. `work/attendance` owns preview interpretation, selection, deduplication, import, and day-off effects.
|
||||||
- Attendance time and Task work time remain separate. No branch may make one mutate or prove the other.
|
- Attendance time and Task work time remain separate. No branch may make one mutate or prove the other.
|
||||||
|
|||||||
@@ -70,11 +70,6 @@ Use exactly these persistent branches unless the user changes the plan:
|
|||||||
| `work/attendance` | Policy, calendar, attendance, corrections, leave, metrics |
|
| `work/attendance` | Policy, calendar, attendance, corrections, leave, metrics |
|
||||||
| `work/reports-ui` | Shared Thymeleaf UI, dashboards, reports, exports |
|
| `work/reports-ui` | Shared Thymeleaf UI, dashboards, reports, exports |
|
||||||
|
|
||||||
For a targeted repair outside the next iteration, create a clean isolated
|
|
||||||
`work/fix/<feature>/<what-fix>` worktree from the taskmaster-verified current
|
|
||||||
`main`. Do not use `work/<feature>/fix/<what-fix>`: the persistent
|
|
||||||
`work/<feature>` ref already occupies that Git ref prefix.
|
|
||||||
|
|
||||||
Create one isolated worktree per branch. Give each implementation agent explicit ownership, tell it other agents share the repository, forbid reverting others' work, require medium-milestone local commits, and forbid push unless separately authorized.
|
Create one isolated worktree per branch. Give each implementation agent explicit ownership, tell it other agents share the repository, forbid reverting others' work, require medium-milestone local commits, and forbid push unless separately authorized.
|
||||||
|
|
||||||
Before any owner edits its module, require it to:
|
Before any owner edits its module, require it to:
|
||||||
|
|||||||
@@ -120,7 +120,6 @@ For a multi-branch iteration:
|
|||||||
|
|
||||||
- Use one worktree and one named owner/subagent per branch. Tell every owner that other agents share the repository and it must not revert others' work.
|
- Use one worktree and one named owner/subagent per branch. Tell every owner that other agents share the repository and it must not revert others' work.
|
||||||
- Before starting assigned module work, every owner verifies its worktree is clean, fetches or uses the taskmaster-verified latest `main`, and fast-forwards its persistent branch to that exact main SHA. Do not build new work on a stale pre-integration branch, and do not use a merge that would rewrite or discard branch history.
|
- Before starting assigned module work, every owner verifies its worktree is clean, fetches or uses the taskmaster-verified latest `main`, and fast-forwards its persistent branch to that exact main SHA. Do not build new work on a stale pre-integration branch, and do not use a merge that would rewrite or discard branch history.
|
||||||
- A targeted repair uses a clean, isolated `work/fix/<feature>/<what-fix>` branch and worktree from the taskmaster-verified current `main`. Do not use `work/<feature>/fix/<what-fix>`: the persistent `work/<feature>` ref already occupies that Git ref prefix.
|
|
||||||
- Establish and commit the platform foundation before dependent persistence work.
|
- Establish and commit the platform foundation before dependent persistence work.
|
||||||
- Exchange only full immutable SHAs from clean worktrees; never merge a moving branch or ambiguous short SHA.
|
- Exchange only full immutable SHAs from clean worktrees; never merge a moving branch or ambiguous short SHA.
|
||||||
- Preserve branch ownership. Request a producer-owned service/DTO boundary instead of reading its tables from a consumer.
|
- Preserve branch ownership. Request a producer-owned service/DTO boundary instead of reading its tables from a consumer.
|
||||||
|
|||||||
@@ -62,14 +62,6 @@ npm ci
|
|||||||
npm run build
|
npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
### Use an isolated repair branch
|
|
||||||
|
|
||||||
For a targeted repair, start a clean worktree from the taskmaster-verified
|
|
||||||
current `main` on `work/fix/<feature>/<what-fix>`. Keep it separate from the
|
|
||||||
five persistent `work/<feature>` branches. Do not use
|
|
||||||
`work/<feature>/fix/<what-fix>` because the persistent `work/<feature>` ref
|
|
||||||
already occupies that Git ref prefix.
|
|
||||||
|
|
||||||
## 3. Start the development containers
|
## 3. Start the development containers
|
||||||
|
|
||||||
### PostgreSQL 18.4
|
### PostgreSQL 18.4
|
||||||
|
|||||||
@@ -79,7 +79,6 @@ The product joins attendance oversight and Project delivery without pretending t
|
|||||||
3. **Attendance and Project work stay distinct.** The product may report them together, but one never derives or proves the other.
|
3. **Attendance and Project work stay distinct.** The product may report them together, but one never derives or proves the other.
|
||||||
4. **Deadlines are enforced at every path.** Scheduled workers improve timeliness, while request-time guards preserve correctness when scheduling is late.
|
4. **Deadlines are enforced at every path.** Scheduled workers improve timeliness, while request-time guards preserve correctness when scheduling is late.
|
||||||
5. **Prefer explicit, reviewable operations.** Feature-owned controller/service/repository flows, constrained state transitions, focused integrations, and shared report datasets serve clarity over speculative machinery.
|
5. **Prefer explicit, reviewable operations.** Feature-owned controller/service/repository flows, constrained state transitions, focused integrations, and shared report datasets serve clarity over speculative machinery.
|
||||||
6. **Fixes preserve branch ownership.** A targeted repair uses a clean `work/fix/<feature>/<what-fix>` branch from verified `main`, not `work/<feature>/fix/<what-fix>`; persistent `work/<feature>` refs already occupy that Git ref prefix.
|
|
||||||
|
|
||||||
## Accessibility & Inclusion
|
## Accessibility & Inclusion
|
||||||
|
|
||||||
|
|||||||
@@ -126,12 +126,6 @@ companion record under [`docs/tests`](docs/tests/README.md).
|
|||||||
| `work/attendance` | Policy, calendar, attendance workflows |
|
| `work/attendance` | Policy, calendar, attendance workflows |
|
||||||
| `work/reports-ui` | Shared UI, dashboards, reporting presentation |
|
| `work/reports-ui` | Shared UI, dashboards, reporting presentation |
|
||||||
|
|
||||||
For a targeted repair, create a clean isolated branch and worktree from the
|
|
||||||
taskmaster-verified current `main` named
|
|
||||||
`work/fix/<feature>/<what-fix>`. Do not nest it as
|
|
||||||
`work/<feature>/fix/<what-fix>`: the persistent `work/<feature>` ref already
|
|
||||||
uses that Git ref prefix.
|
|
||||||
|
|
||||||
Iteration 2 work must start from the merged Iteration 1 `main`, continue with
|
Iteration 2 work must start from the merged Iteration 1 `main`, continue with
|
||||||
strict RED-to-GREEN TDD, add Javadoc during implementation, and update the
|
strict RED-to-GREEN TDD, add Javadoc during implementation, and update the
|
||||||
matching Markdown evidence record before each milestone commit.
|
matching Markdown evidence record before each milestone commit.
|
||||||
|
|||||||
@@ -120,12 +120,6 @@ Simple configuration or documentation changes use the smallest useful shell
|
|||||||
check, followed by the affected Maven suite. Do not create an artificial Java
|
check, followed by the affected Maven suite. Do not create an artificial Java
|
||||||
test only to check that a text file exists.
|
test only to check that a text file exists.
|
||||||
|
|
||||||
Run that check from the clean targeted-fix branch named
|
|
||||||
`work/fix/<feature>/<what-fix>` when repairing one feature. Do not use
|
|
||||||
`work/<feature>/fix/<what-fix>`: a persistent `work/<feature>` ref already
|
|
||||||
occupies that Git ref prefix. Record the expected RED and the matching GREEN
|
|
||||||
shell output in the evidence record.
|
|
||||||
|
|
||||||
## 4. Useful commands
|
## 4. Useful commands
|
||||||
|
|
||||||
Run one test method:
|
Run one test method:
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
# Access, Navigation, Icon, and Intern Picker Fix Plan
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
Record the durable workflow for the targeted fixes in this plan. This plan does
|
|
||||||
not change product behavior, dependencies, schemas, or the five persistent
|
|
||||||
feature-branch ownership areas.
|
|
||||||
|
|
||||||
## Implementation steps
|
|
||||||
|
|
||||||
1. Prove RED: the contributor guides lack the realizable repair-branch name.
|
|
||||||
2. Add one branch rule to contributor guides, design records, plans, and tracked
|
|
||||||
coordination authority: `work/fix/<feature>/<what-fix>` from verified
|
|
||||||
`main`.
|
|
||||||
3. State why `work/<feature>/fix/<what-fix>` is invalid while its persistent
|
|
||||||
`work/<feature>` ref exists.
|
|
||||||
4. Regenerate the local SRS after amending the existing operational requirement;
|
|
||||||
do not add a requirement ID or a use case.
|
|
||||||
5. Prove GREEN with the executable six-guide regression that independently
|
|
||||||
rejects a positive nested-form recommendation in every guide,
|
|
||||||
coordination-authority consistency, requirement/use-case counts, local-link
|
|
||||||
resolution, and an immutable base-to-candidate whitespace check. Commit the
|
|
||||||
tracked guidance locally; do not push or merge.
|
|
||||||
|
|
||||||
## Exit criteria
|
|
||||||
|
|
||||||
- The tracked guides, design record, implementation plan, root coordination
|
|
||||||
authority, and evidence record agree on the same repair-branch spelling.
|
|
||||||
- The local authoritative, explained, simple, and generated SRS catalogues keep
|
|
||||||
exactly 260 unique requirement IDs and the SRS keeps 14 use cases.
|
|
||||||
- The forbidden nested form is documented only as forbidden, not as a usable
|
|
||||||
branch name.
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
# Design Record: Durable Fix-Branch Workflow
|
|
||||||
|
|
||||||
- **Date:** 2026-08-15
|
|
||||||
- **Status:** Accepted
|
|
||||||
- **Related plan:** [Access, Navigation, Icon, and Intern Picker Fix Plan](../plans/2026-08-15-access-navigation-icon-intern-picker.md)
|
|
||||||
|
|
||||||
## Context
|
|
||||||
|
|
||||||
The repository keeps five persistent feature refs: `work/platform`,
|
|
||||||
`work/projects`, `work/tasks`, `work/attendance`, and `work/reports-ui`.
|
|
||||||
A proposed nested repair name such as `work/platform/fix/example` cannot coexist
|
|
||||||
with the existing `work/platform` ref because Git cannot use one ref as both a
|
|
||||||
leaf and a prefix.
|
|
||||||
|
|
||||||
## Decision
|
|
||||||
|
|
||||||
Use `work/fix/<feature>/<what-fix>` for each targeted repair. Create its clean,
|
|
||||||
isolated worktree from the taskmaster-verified current `main`. The `<feature>`
|
|
||||||
segment identifies the owning persistent area; it does not nest below that
|
|
||||||
persistent branch.
|
|
||||||
|
|
||||||
The forbidden form is `work/<feature>/fix/<what-fix>`. A repair owner preserves
|
|
||||||
other worktrees, records RED and GREEN evidence, commits locally, and does not
|
|
||||||
push or merge without separate authority.
|
|
||||||
|
|
||||||
## Consequences
|
|
||||||
|
|
||||||
- Persistent feature branches remain available for their iteration ownership.
|
|
||||||
- A repair can be reviewed and handed off as one immutable branch head.
|
|
||||||
- Contributor documentation, local coordination authority, and generated SRS
|
|
||||||
traceability use the same spelling.
|
|
||||||
|
|
||||||
## Validation
|
|
||||||
|
|
||||||
The executable documentation validator checks the exact approved statement in
|
|
||||||
each of the six tracked guides and independently rejects an injected positive
|
|
||||||
nested-branch recommendation in every guide. The copied root coordination
|
|
||||||
authority uses the same rule and is checked separately for consistency. The
|
|
||||||
evidence record also verifies requirement counts, generated SRS use-case count,
|
|
||||||
and local Markdown links.
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
# Test Evidence: durable fix-branch documentation workflow
|
|
||||||
|
|
||||||
- **Test type:** Unit (documentation contract)
|
|
||||||
- **Requirement IDs:** `OPS-019`, `TST-009`, `TST-010`
|
|
||||||
- **Scenario IDs:** `AC-TST-001`
|
|
||||||
- **Test class/method:** `scripts/verify-fix-branch-workflow.cjs --self-test`
|
|
||||||
- **Implementation commit:** `f013ad7707b36959ddca891fe0d52f81bba3ee80`
|
|
||||||
- **Review-fix commit:** `d617769499362e92d058684501af3c1ae6b145b0`
|
|
||||||
|
|
||||||
## Protected behavior
|
|
||||||
|
|
||||||
Targeted repairs use the realizable `work/fix/<feature>/<what-fix>` branch and
|
|
||||||
clean worktree from taskmaster-verified `main`. Contributor guidance must reject
|
|
||||||
the impossible `work/<feature>/fix/<what-fix>` form while persistent
|
|
||||||
`work/<feature>` refs exist.
|
|
||||||
|
|
||||||
## Test method
|
|
||||||
|
|
||||||
Use the tracked Node validator rather than an artificial Java test. It requires
|
|
||||||
the exact approved statement in each of the six guides, then self-tests that an
|
|
||||||
extra positive nested-branch recommendation is rejected. The original RED
|
|
||||||
proves the required branch name was absent from the four contributor guides;
|
|
||||||
the review-fix RED proves the executable regression was absent.
|
|
||||||
|
|
||||||
## Hand-derived expected result
|
|
||||||
|
|
||||||
The required fix-branch spelling appears exactly once in each of the six tracked
|
|
||||||
documentation artifacts, and the only nested-form reference is inside that
|
|
||||||
artifact's exact approved statement. A simulated positive nested-branch
|
|
||||||
recommendation must fail. The existing SRS generator must still report 260
|
|
||||||
requirements and 14 use cases.
|
|
||||||
|
|
||||||
## RED
|
|
||||||
|
|
||||||
**Command**
|
|
||||||
|
|
||||||
```text
|
|
||||||
rg -n -F 'work/fix/<feature>/<what-fix>' AGENTS.md README.md DEVELOPMENT.md TESTING.md
|
|
||||||
```
|
|
||||||
|
|
||||||
**Observed result**
|
|
||||||
|
|
||||||
```text
|
|
||||||
exit 1; no matching lines
|
|
||||||
```
|
|
||||||
|
|
||||||
The failure was expected: the required realizable repair-branch rule was absent
|
|
||||||
before this documentation change.
|
|
||||||
|
|
||||||
### Review-fix RED
|
|
||||||
|
|
||||||
**Command**
|
|
||||||
|
|
||||||
```text
|
|
||||||
node scripts/verify-fix-branch-workflow.cjs --self-test
|
|
||||||
```
|
|
||||||
|
|
||||||
**Observed result**
|
|
||||||
|
|
||||||
```text
|
|
||||||
exit 1
|
|
||||||
Error: Cannot find module '.../scripts/verify-fix-branch-workflow.cjs'
|
|
||||||
```
|
|
||||||
|
|
||||||
The executable regression required to reject a positive nested-branch
|
|
||||||
recommendation did not exist.
|
|
||||||
|
|
||||||
## GREEN
|
|
||||||
|
|
||||||
**Command**
|
|
||||||
|
|
||||||
```text
|
|
||||||
node scripts/verify-fix-branch-workflow.cjs --self-test
|
|
||||||
```
|
|
||||||
|
|
||||||
**Observed result**
|
|
||||||
|
|
||||||
```text
|
|
||||||
Fix-branch workflow documentation: 6 approved statements validated
|
|
||||||
Positive nested branch recommendation: rejected
|
|
||||||
```
|
|
||||||
|
|
||||||
## Affected suite
|
|
||||||
|
|
||||||
**Command and result**
|
|
||||||
|
|
||||||
```text
|
|
||||||
node labtimesheet-docs-hub/ui-mockups/build-srs.cjs
|
|
||||||
node -e 'const fs=require("node:fs"); const checks=[["authoritative","labtimesheet-docs-hub/requirements-specification.md",/^\| ([A-Z]{2,4}-\d{3}) \|/gm],["explained","labtimesheet-docs-hub/explained/requirements-specification.md",/^\| ([A-Z]{2,4}-\d{3}) \|/gm],["simple","labtimesheet-docs-hub/explained/requirements-specification-simple.md",/^- \*\*([A-Z]{2,4}-\d{3}):\*\*/gm],["generated SRS","labtimesheet-docs-hub/software-requirements-specification.md",/^\| ([A-Z]{2,4}-\d{3}) \|/gm]]; for (const [name,file,pattern] of checks) { const ids=[...fs.readFileSync(file,"utf8").matchAll(pattern)].map(match=>match[1]); if (ids.length !== 260 || new Set(ids).size !== 260) throw new Error(`${name}: ${ids.length} rows, ${new Set(ids).size} unique`); console.log(`${name}: ${ids.length} rows, ${new Set(ids).size} unique IDs`); } const srs=fs.readFileSync("labtimesheet-docs-hub/software-requirements-specification.md","utf8"); const useCases=(srs.match(/^### 5\.\d+ UC-\d{2} —/gm)||[]).length; if (useCases !== 14) throw new Error(`SRS use cases: ${useCases}`); console.log(`generated SRS: ${useCases} use cases`);'
|
|
||||||
node -e 'const fs=require("node:fs"); const path=require("node:path"); let checked=0; const broken=[]; for (const file of process.argv.slice(1)) { const text=fs.readFileSync(file,"utf8"); for (const match of text.matchAll(/!?\[[^\]]*\]\(([^)]+)\)/g)) { const target=match[1].trim().replace(/^<|>$/g,"").split("#")[0].split("?")[0]; if (!target || /^[a-z][a-z0-9+.-]*:/i.test(target) || target.startsWith("//")) continue; checked += 1; if (!fs.existsSync(path.resolve(path.dirname(file), decodeURIComponent(target)))) broken.push(`${file}: ${target}`); } } if (broken.length) throw new Error(`Broken local Markdown links:\n${broken.join("\n")}`); console.log(`Local Markdown links: ${checked} resolved`);' AGENTS.md README.md DEVELOPMENT.md TESTING.md docs/superpowers/specs/2026-08-15-access-navigation-icon-intern-picker-design.md docs/superpowers/plans/2026-08-15-access-navigation-icon-intern-picker.md docs/tests/unit/fix-branch-workflow-documentation.md
|
|
||||||
git diff --check 8be1b754e188367b260981718a5d33fc2d4d8a3b d617769499362e92d058684501af3c1ae6b145b0
|
|
||||||
```
|
|
||||||
|
|
||||||
The SRS regeneration and count assertion ran from the main root because the
|
|
||||||
ignored requirements hub is local authority there. The link assertion and
|
|
||||||
the exact base-to-candidate `git diff --check` ran from this fix worktree; the
|
|
||||||
SRS generator also rejects a broken local SRS target before it writes the
|
|
||||||
generated file.
|
|
||||||
|
|
||||||
```text
|
|
||||||
Wrote labtimesheet-docs-hub/software-requirements-specification.md
|
|
||||||
Requirements: 260; use cases: 14; screens: 48; mockup embeds: 48
|
|
||||||
authoritative: 260 rows, 260 unique IDs
|
|
||||||
explained: 260 rows, 260 unique IDs
|
|
||||||
simple: 260 rows, 260 unique IDs
|
|
||||||
generated SRS: 260 rows, 260 unique IDs
|
|
||||||
generated SRS: 14 use cases
|
|
||||||
Fix-branch workflow documentation: 6 approved statements validated
|
|
||||||
Positive nested branch recommendation: rejected
|
|
||||||
Local Markdown links: 7 resolved
|
|
||||||
git diff --check 8be1b754e188367b260981718a5d33fc2d4d8a3b d617769499362e92d058684501af3c1ae6b145b0: exit 0
|
|
||||||
```
|
|
||||||
|
|
||||||
## External-test boundaries
|
|
||||||
|
|
||||||
This documentation contract does not create or manipulate Git branches, start
|
|
||||||
the application, or replace branch-owner review. It validates the durable rule
|
|
||||||
and SRS traceability only; a taskmaster still authorizes branch creation,
|
|
||||||
integration, and any push.
|
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
# Test Evidence: persistent Admin SMTP settings navigation
|
||||||
|
|
||||||
|
- **Test type:** Web
|
||||||
|
- **Requirement IDs:** `ACC-007`, `INT-001`, `AUTH-002`, `UI-003`, `UI-008`, `UI-009`, `UI-010`
|
||||||
|
- **Scenario IDs:** `AC-ACC-003`, `AC-UI-002`, `AC-UI-003`
|
||||||
|
- **Test class/method:** `com.lab.labtimesheet.feature.reporting.controller.DashboardControllerWebTest`, `com.lab.labtimesheet.feature.reporting.controller.RoleDashboardWebIntegrationTest#mentorAndInternDashboardsRenderRealScopedProjectTaskAndAttendanceData`
|
||||||
|
- **Implementation commit:** pending
|
||||||
|
|
||||||
|
## Protected behavior
|
||||||
|
|
||||||
|
An Admin always receives an SMTP settings destination in the shared sidebar, whether SMTP is restricted or active. The restricted-installation warning remains conditional. Mentor and Intern sidebars never expose the Admin-only destination, and the Admin link uses the local settings sprite plus the established collapsed-sidebar tooltip.
|
||||||
|
|
||||||
|
## Test method
|
||||||
|
|
||||||
|
The MVC slice renders the real dashboard controller, Spring Security Thymeleaf dialect, and shared layout with only the SMTP state and dashboard query services mocked at their public boundaries. It checks both Admin SMTP states and the active-SMTP Mentor/Intern views. The PostgreSQL 18.4 integration test activates SMTP through the real service, extracts rendered navigation links, requires the Admin SMTP route only for Admin, and follows every discovered link through the real controller/security stack.
|
||||||
|
|
||||||
|
## Hand-derived expected result
|
||||||
|
|
||||||
|
With SMTP restricted, an Admin dashboard contains the existing warning and a sidebar link to `/admin/smtp` identified by `data-tooltip="SMTP settings"`. After SMTP activation, the warning is absent but that same sidebar link remains. Mentor and Intern dashboards omit the SMTP-settings tooltip and route. The activated Admin link resolves successfully when followed.
|
||||||
|
|
||||||
|
## RED
|
||||||
|
|
||||||
|
**Command**
|
||||||
|
|
||||||
|
```text
|
||||||
|
JAVA_HOME=/opt/homebrew/opt/openjdk@25 PATH=/opt/homebrew/opt/openjdk@25/bin:/opt/homebrew/opt/node@24/bin:$PATH ./mvnw -DargLine=-javaagent:/Users/sechmachine/.m2/repository/net/bytebuddy/byte-buddy-agent/1.18.10/byte-buddy-agent-1.18.10.jar -Dtest=DashboardControllerWebTest test
|
||||||
|
```
|
||||||
|
|
||||||
|
**Observed result**
|
||||||
|
|
||||||
|
```text
|
||||||
|
Tests run: 6, Failures: 2, Errors: 0, Skipped: 0
|
||||||
|
DashboardControllerWebTest.adminRendersAdminDashboardForAuthenticatedIdentity: expected data-tooltip="SMTP settings" but it was absent.
|
||||||
|
DashboardControllerWebTest.activeSmtpKeepsAdminDashboardFreeOfTheRestrictedInstallationWarning: expected href="/admin/smtp" data-tooltip="SMTP settings" but it was absent.
|
||||||
|
BUILD FAILURE
|
||||||
|
```
|
||||||
|
|
||||||
|
The Mentor and Intern active-SMTP assertions passed in this RED run, so the failures establish the missing Admin navigation rather than an incorrect role fixture.
|
||||||
|
|
||||||
|
**Command**
|
||||||
|
|
||||||
|
```text
|
||||||
|
JAVA_HOME=/opt/homebrew/opt/openjdk@25 PATH=/opt/homebrew/opt/openjdk@25/bin:/opt/homebrew/opt/node@24/bin:$PATH DOCKER_HOST=unix:///Users/sechmachine/.orbstack/run/docker.sock ./mvnw -DargLine=-javaagent:/Users/sechmachine/.m2/repository/net/bytebuddy/byte-buddy-agent/1.18.10/byte-buddy-agent-1.18.10.jar -Dtest=RoleDashboardWebIntegrationTest test
|
||||||
|
```
|
||||||
|
|
||||||
|
**Observed result**
|
||||||
|
|
||||||
|
```text
|
||||||
|
PostgreSQL: 18.4 Testcontainer
|
||||||
|
Tests run: 1, Failures: 1, Errors: 0, Skipped: 0
|
||||||
|
Expected Admin visible navigation paths to contain /admin/smtp, but rendered paths were /dashboard, /admin/accounts/new, /attendance/calendar.
|
||||||
|
BUILD FAILURE
|
||||||
|
```
|
||||||
|
|
||||||
|
## GREEN
|
||||||
|
|
||||||
|
**Command**
|
||||||
|
|
||||||
|
```text
|
||||||
|
JAVA_HOME=/opt/homebrew/opt/openjdk@25 PATH=/opt/homebrew/opt/openjdk@25/bin:/opt/homebrew/opt/node@24/bin:$PATH ./mvnw -DargLine=-javaagent:/Users/sechmachine/.m2/repository/net/bytebuddy/byte-buddy-agent/1.18.10/byte-buddy-agent-1.18.10.jar -Dtest=DashboardControllerWebTest test
|
||||||
|
|
||||||
|
JAVA_HOME=/opt/homebrew/opt/openjdk@25 PATH=/opt/homebrew/opt/openjdk@25/bin:/opt/homebrew/opt/node@24/bin:$PATH DOCKER_HOST=unix:///Users/sechmachine/.orbstack/run/docker.sock ./mvnw -DargLine=-javaagent:/Users/sechmachine/.m2/repository/net/bytebuddy/byte-buddy-agent/1.18.10/byte-buddy-agent-1.18.10.jar -Dtest=RoleDashboardWebIntegrationTest test
|
||||||
|
```
|
||||||
|
|
||||||
|
**Observed result**
|
||||||
|
|
||||||
|
```text
|
||||||
|
DashboardControllerWebTest: Tests run: 6, Failures: 0, Errors: 0, Skipped: 0
|
||||||
|
RoleDashboardWebIntegrationTest: PostgreSQL 18.4 Testcontainer; Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
|
||||||
|
BUILD SUCCESS
|
||||||
|
```
|
||||||
|
|
||||||
|
## Affected suite
|
||||||
|
|
||||||
|
**Command and result**
|
||||||
|
|
||||||
|
```text
|
||||||
|
PATH=/opt/homebrew/opt/node@24/bin:$PATH node --version && PATH=/opt/homebrew/opt/node@24/bin:$PATH npm --version && PATH=/opt/homebrew/opt/node@24/bin:$PATH npm ci && PATH=/opt/homebrew/opt/node@24/bin:$PATH npm run build
|
||||||
|
Node v24.19.0; npm 11.17.0; Tailwind CSS v4.3.3
|
||||||
|
BUILD SUCCESS
|
||||||
|
|
||||||
|
JAVA_HOME=/opt/homebrew/opt/openjdk@25 PATH=/opt/homebrew/opt/openjdk@25/bin:/opt/homebrew/opt/node@24/bin:$PATH DOCKER_HOST=unix:///Users/sechmachine/.orbstack/run/docker.sock ./mvnw -DargLine=-javaagent:/Users/sechmachine/.m2/repository/net/bytebuddy/byte-buddy-agent/1.18.10/byte-buddy-agent-1.18.10.jar -Dtest=SecurityResponseIntegrationTest,BootstrapOnboardingWebIntegrationTest,AccountWebIntegrationTest,SmtpOnboardingWebIntegrationTest,RoleDashboardWebIntegrationTest,UiContractWebTest,AccountTemplateIntegrationTest,AttendanceTemplateIntegrationTest,DashboardControllerWebTest,DashboardTemplateWebTest,ProjectTaskFormAccessibilityWebTest,SharedErrorTemplateWebTest,ProjectControllerTest,TaskControllerTest,AttendanceControllerTest test
|
||||||
|
PostgreSQL 18.4 Testcontainers; Tests run: 81, Failures: 0, Errors: 0, Skipped: 0
|
||||||
|
BUILD SUCCESS
|
||||||
|
```
|
||||||
|
|
||||||
|
## External-test boundaries
|
||||||
|
|
||||||
|
MockMvc proves rendered role/state visibility, while the PostgreSQL integration test proves the real Admin route follow. They do not render the collapsed rail or inspect pixels, browser focus placement, or tooltip positioning; the existing CSS and local settings sprite are reused unchanged. Server-side direct-URL authorization remains the existing `/admin/**` Admin-only security rule and is not broadened by this layout-only change.
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
const assert = require('node:assert/strict');
|
|
||||||
const fs = require('node:fs');
|
|
||||||
const path = require('node:path');
|
|
||||||
|
|
||||||
const repositoryRoot = path.resolve(__dirname, '..');
|
|
||||||
const validForm = '`work/fix/<feature>/<what-fix>`';
|
|
||||||
const invalidForm = '`work/<feature>/fix/<what-fix>`';
|
|
||||||
const documents = [
|
|
||||||
{
|
|
||||||
file: 'AGENTS.md',
|
|
||||||
approved: '- A targeted repair uses a clean, isolated `work/fix/<feature>/<what-fix>` branch and worktree from the taskmaster-verified current `main`. Do not use `work/<feature>/fix/<what-fix>`: the persistent `work/<feature>` ref already occupies that Git ref prefix.'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
file: 'README.md',
|
|
||||||
approved: 'For a targeted repair, create a clean isolated branch and worktree from the\ntaskmaster-verified current `main` named\n`work/fix/<feature>/<what-fix>`. Do not nest it as\n`work/<feature>/fix/<what-fix>`: the persistent `work/<feature>` ref already\nuses that Git ref prefix.'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
file: 'DEVELOPMENT.md',
|
|
||||||
approved: 'For a targeted repair, start a clean worktree from the taskmaster-verified\ncurrent `main` on `work/fix/<feature>/<what-fix>`. Keep it separate from the\nfive persistent `work/<feature>` branches. Do not use\n`work/<feature>/fix/<what-fix>` because the persistent `work/<feature>` ref\nalready occupies that Git ref prefix.'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
file: 'TESTING.md',
|
|
||||||
approved: 'Run that check from the clean targeted-fix branch named\n`work/fix/<feature>/<what-fix>` when repairing one feature. Do not use\n`work/<feature>/fix/<what-fix>`: a persistent `work/<feature>` ref already\noccupies that Git ref prefix. Record the expected RED and the matching GREEN\nshell output in the evidence record.'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
file: 'docs/superpowers/specs/2026-08-15-access-navigation-icon-intern-picker-design.md',
|
|
||||||
approved: 'Use `work/fix/<feature>/<what-fix>` for each targeted repair. Create its clean,\nisolated worktree from the taskmaster-verified current `main`. The `<feature>`\nsegment identifies the owning persistent area; it does not nest below that\npersistent branch.\n\nThe forbidden form is `work/<feature>/fix/<what-fix>`. A repair owner preserves\nother worktrees, records RED and GREEN evidence, commits locally, and does not\npush or merge without separate authority.'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
file: 'docs/superpowers/plans/2026-08-15-access-navigation-icon-intern-picker.md',
|
|
||||||
approved: '2. Add one branch rule to contributor guides, design records, plans, and tracked\n coordination authority: `work/fix/<feature>/<what-fix>` from verified\n `main`.\n3. State why `work/<feature>/fix/<what-fix>` is invalid while its persistent\n `work/<feature>` ref exists.'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
/** Validates that every tracked guide contains only its approved branch wording. */
|
|
||||||
function validate(contents) {
|
|
||||||
const failures = [];
|
|
||||||
|
|
||||||
for (const {file, approved} of documents) {
|
|
||||||
const content = contents.get(file);
|
|
||||||
if (count(content, validForm) !== 1) failures.push(`${file} must contain ${validForm} exactly once`);
|
|
||||||
if (!content.includes(approved)) failures.push(`${file} is missing its approved branch workflow statement`);
|
|
||||||
|
|
||||||
const outsideApprovedStatement = content.replace(approved, '');
|
|
||||||
if (outsideApprovedStatement.includes(validForm) || outsideApprovedStatement.includes(invalidForm)) {
|
|
||||||
failures.push(`${file} contains an unapproved branch-form reference`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (failures.length) throw new Error(failures.join('\n'));
|
|
||||||
}
|
|
||||||
|
|
||||||
function count(content, value) {
|
|
||||||
return content.split(value).length - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
function readContents() {
|
|
||||||
return new Map(documents.map(({file}) => [file, fs.readFileSync(path.join(repositoryRoot, file), 'utf8')]));
|
|
||||||
}
|
|
||||||
|
|
||||||
const contents = readContents();
|
|
||||||
validate(contents);
|
|
||||||
|
|
||||||
if (process.argv.includes('--self-test')) {
|
|
||||||
for (const {file} of documents) {
|
|
||||||
const positiveRecommendation = new Map(contents);
|
|
||||||
positiveRecommendation.set(file, `${contents.get(file)}\nUse ${invalidForm} for a targeted repair.\n`);
|
|
||||||
assert.throws(
|
|
||||||
() => validate(positiveRecommendation),
|
|
||||||
(error) => error instanceof Error
|
|
||||||
&& error.message.includes(`${file} contains an unapproved branch-form reference`)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Fix-branch workflow documentation: ${documents.length} approved statements validated`);
|
|
||||||
if (process.argv.includes('--self-test')) {
|
|
||||||
console.log(`Positive nested branch recommendations: ${documents.length}/${documents.length} rejected`);
|
|
||||||
}
|
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
<li><a class="nav-link" th:href="@{/dashboard}" data-tooltip="Overview" th:attr="aria-current=${activeNav == 'dashboard'} ? 'page' : null">
|
<li><a class="nav-link" th:href="@{/dashboard}" data-tooltip="Overview" th:attr="aria-current=${activeNav == 'dashboard'} ? 'page' : null">
|
||||||
<svg class="nav-icon" aria-hidden="true"><use th:href="@{/assets/icons.svg#layout-dashboard}"></use></svg><span class="sidebar-label">Overview</span></a></li>
|
<svg class="nav-icon" aria-hidden="true"><use th:href="@{/assets/icons.svg#layout-dashboard}"></use></svg><span class="sidebar-label">Overview</span></a></li>
|
||||||
<li sec:authorize="hasRole('ADMIN')"><a class="nav-link" th:href="@{/admin/accounts/new}" data-tooltip="Accounts" th:attr="aria-current=${activeNav == 'accounts'} ? 'page' : null"><svg class="nav-icon" aria-hidden="true"><use th:href="@{/assets/icons.svg#users}"></use></svg><span class="sidebar-label">Accounts</span></a></li>
|
<li sec:authorize="hasRole('ADMIN')"><a class="nav-link" th:href="@{/admin/accounts/new}" data-tooltip="Accounts" th:attr="aria-current=${activeNav == 'accounts'} ? 'page' : null"><svg class="nav-icon" aria-hidden="true"><use th:href="@{/assets/icons.svg#users}"></use></svg><span class="sidebar-label">Accounts</span></a></li>
|
||||||
|
<li sec:authorize="hasRole('ADMIN')"><a class="nav-link" th:href="@{/admin/smtp}" data-tooltip="SMTP settings" th:attr="aria-current=${activeNav == 'smtp'} ? 'page' : null"><svg class="nav-icon" aria-hidden="true"><use th:href="@{/assets/icons.svg#settings}"></use></svg><span class="sidebar-label">SMTP settings</span></a></li>
|
||||||
<li sec:authorize="hasRole('ADMIN')"><a class="nav-link" th:href="@{/attendance/calendar}" data-tooltip="Global calendar" th:attr="aria-current=${activeNav == 'calendar'} ? 'page' : null"><svg class="nav-icon" aria-hidden="true"><use th:href="@{/assets/icons.svg#calendar-days}"></use></svg><span class="sidebar-label">Global calendar</span></a></li>
|
<li sec:authorize="hasRole('ADMIN')"><a class="nav-link" th:href="@{/attendance/calendar}" data-tooltip="Global calendar" th:attr="aria-current=${activeNav == 'calendar'} ? 'page' : null"><svg class="nav-icon" aria-hidden="true"><use th:href="@{/assets/icons.svg#calendar-days}"></use></svg><span class="sidebar-label">Global calendar</span></a></li>
|
||||||
<li sec:authorize="hasRole('MENTOR')"><a class="nav-link" th:href="@{/projects}" data-tooltip="Owned Projects" th:attr="aria-current=${activeNav == 'projects'} ? 'page' : null"><svg class="nav-icon" aria-hidden="true"><use th:href="@{/assets/icons.svg#folder-kanban}"></use></svg><span class="sidebar-label">Owned Projects</span></a></li>
|
<li sec:authorize="hasRole('MENTOR')"><a class="nav-link" th:href="@{/projects}" data-tooltip="Owned Projects" th:attr="aria-current=${activeNav == 'projects'} ? 'page' : null"><svg class="nav-icon" aria-hidden="true"><use th:href="@{/assets/icons.svg#folder-kanban}"></use></svg><span class="sidebar-label">Owned Projects</span></a></li>
|
||||||
<li sec:authorize="hasRole('INTERN')"><a class="nav-link" th:href="@{/attendance}" data-tooltip="My attendance" th:attr="aria-current=${activeNav == 'attendance'} ? 'page' : null"><svg class="nav-icon" aria-hidden="true"><use th:href="@{/assets/icons.svg#clock}"></use></svg><span class="sidebar-label">My attendance</span></a></li>
|
<li sec:authorize="hasRole('INTERN')"><a class="nav-link" th:href="@{/attendance}" data-tooltip="My attendance" th:attr="aria-current=${activeNav == 'attendance'} ? 'page' : null"><svg class="nav-icon" aria-hidden="true"><use th:href="@{/assets/icons.svg#clock}"></use></svg><span class="sidebar-label">My attendance</span></a></li>
|
||||||
|
|||||||
+14
-4
@@ -44,7 +44,9 @@ class DashboardControllerWebTest {
|
|||||||
.andExpect(model().attribute("dashboard", dashboard))
|
.andExpect(model().attribute("dashboard", dashboard))
|
||||||
.andExpect(content().string(org.hamcrest.Matchers.containsString(
|
.andExpect(content().string(org.hamcrest.Matchers.containsString(
|
||||||
"This installation remains restricted until tested SMTP is active.")))
|
"This installation remains restricted until tested SMTP is active.")))
|
||||||
.andExpect(content().string(org.hamcrest.Matchers.containsString("href=\"/admin/smtp\"")));
|
.andExpect(content().string(org.hamcrest.Matchers.containsString("href=\"/admin/smtp\"")))
|
||||||
|
.andExpect(content().string(org.hamcrest.Matchers.containsString(
|
||||||
|
"data-tooltip=\"SMTP settings\"")));
|
||||||
|
|
||||||
verify(dashboards).admin("admin@example.test");
|
verify(dashboards).admin("admin@example.test");
|
||||||
}
|
}
|
||||||
@@ -58,20 +60,25 @@ class DashboardControllerWebTest {
|
|||||||
mvc.perform(get("/dashboard").with(user("admin@example.test").roles("ADMIN")))
|
mvc.perform(get("/dashboard").with(user("admin@example.test").roles("ADMIN")))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(content().string(org.hamcrest.Matchers.not(org.hamcrest.Matchers.containsString(
|
.andExpect(content().string(org.hamcrest.Matchers.not(org.hamcrest.Matchers.containsString(
|
||||||
"This installation remains restricted until tested SMTP is active."))));
|
"This installation remains restricted until tested SMTP is active."))))
|
||||||
|
.andExpect(content().string(org.hamcrest.Matchers.containsString(
|
||||||
|
"href=\"/admin/smtp\" data-tooltip=\"SMTP settings\"")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void mentorRendersMentorDashboardForAuthenticatedIdentity() throws Exception {
|
void mentorRendersMentorDashboardForAuthenticatedIdentity() throws Exception {
|
||||||
var dashboard = new DashboardView.Mentor("Mentor", 2, 4, 1);
|
var dashboard = new DashboardView.Mentor("Mentor", 2, 4, 1);
|
||||||
given(dashboards.mentor("mentor@example.test")).willReturn(dashboard);
|
given(dashboards.mentor("mentor@example.test")).willReturn(dashboard);
|
||||||
|
given(smtpConfiguration.hasActiveConfiguration()).willReturn(true);
|
||||||
|
|
||||||
mvc.perform(get("/dashboard").with(user("mentor@example.test").roles("MENTOR")))
|
mvc.perform(get("/dashboard").with(user("mentor@example.test").roles("MENTOR")))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(view().name("dashboard/mentor"))
|
.andExpect(view().name("dashboard/mentor"))
|
||||||
.andExpect(model().attribute("dashboard", dashboard))
|
.andExpect(model().attribute("dashboard", dashboard))
|
||||||
.andExpect(content().string(org.hamcrest.Matchers.not(org.hamcrest.Matchers.containsString(
|
.andExpect(content().string(org.hamcrest.Matchers.not(org.hamcrest.Matchers.containsString(
|
||||||
"This installation remains restricted until tested SMTP is active."))));
|
"This installation remains restricted until tested SMTP is active."))))
|
||||||
|
.andExpect(content().string(org.hamcrest.Matchers.not(org.hamcrest.Matchers.containsString(
|
||||||
|
"data-tooltip=\"SMTP settings\""))));
|
||||||
|
|
||||||
verify(dashboards).mentor("mentor@example.test");
|
verify(dashboards).mentor("mentor@example.test");
|
||||||
}
|
}
|
||||||
@@ -81,11 +88,14 @@ class DashboardControllerWebTest {
|
|||||||
var dashboard = new DashboardView.Intern(
|
var dashboard = new DashboardView.Intern(
|
||||||
"Intern", DashboardView.AttendanceState.NOT_CHECKED_IN, 1, 0, List.of());
|
"Intern", DashboardView.AttendanceState.NOT_CHECKED_IN, 1, 0, List.of());
|
||||||
given(dashboards.intern("intern@example.test")).willReturn(dashboard);
|
given(dashboards.intern("intern@example.test")).willReturn(dashboard);
|
||||||
|
given(smtpConfiguration.hasActiveConfiguration()).willReturn(true);
|
||||||
|
|
||||||
mvc.perform(get("/dashboard").with(user("intern@example.test").roles("INTERN")))
|
mvc.perform(get("/dashboard").with(user("intern@example.test").roles("INTERN")))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(view().name("dashboard/intern"))
|
.andExpect(view().name("dashboard/intern"))
|
||||||
.andExpect(model().attribute("dashboard", dashboard));
|
.andExpect(model().attribute("dashboard", dashboard))
|
||||||
|
.andExpect(content().string(org.hamcrest.Matchers.not(org.hamcrest.Matchers.containsString(
|
||||||
|
"data-tooltip=\"SMTP settings\""))));
|
||||||
|
|
||||||
verify(dashboards).intern("intern@example.test");
|
verify(dashboards).intern("intern@example.test");
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-4
@@ -136,12 +136,12 @@ class RoleDashboardWebIntegrationTest {
|
|||||||
.andExpect(content().string(containsString("BLOCKED")))
|
.andExpect(content().string(containsString("BLOCKED")))
|
||||||
.andExpect(content().string(containsString("20/08/2026")));
|
.andExpect(content().string(containsString("20/08/2026")));
|
||||||
|
|
||||||
followEveryVisibleNavigationLink("admin@example.test", "ADMIN");
|
followEveryVisibleNavigationLink("admin@example.test", "ADMIN", true);
|
||||||
followEveryVisibleNavigationLink("mentor@example.test", "MENTOR");
|
followEveryVisibleNavigationLink("mentor@example.test", "MENTOR", false);
|
||||||
followEveryVisibleNavigationLink("intern@example.test", "INTERN");
|
followEveryVisibleNavigationLink("intern@example.test", "INTERN", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void followEveryVisibleNavigationLink(String email, String role) throws Exception {
|
private void followEveryVisibleNavigationLink(String email, String role, boolean expectsSmtpSettings) throws Exception {
|
||||||
String dashboard = mvc.perform(get("/dashboard").with(user(email).roles(role)))
|
String dashboard = mvc.perform(get("/dashboard").with(user(email).roles(role)))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andReturn()
|
.andReturn()
|
||||||
@@ -153,6 +153,11 @@ class RoleDashboardWebIntegrationTest {
|
|||||||
paths.add(matcher.group(1));
|
paths.add(matcher.group(1));
|
||||||
}
|
}
|
||||||
assertThat(paths).isNotEmpty();
|
assertThat(paths).isNotEmpty();
|
||||||
|
if (expectsSmtpSettings) {
|
||||||
|
assertThat(paths).contains("/admin/smtp");
|
||||||
|
} else {
|
||||||
|
assertThat(paths).doesNotContain("/admin/smtp");
|
||||||
|
}
|
||||||
for (String path : paths) {
|
for (String path : paths) {
|
||||||
mvc.perform(get(path).with(user(email).roles(role)))
|
mvc.perform(get(path).with(user(email).roles(role)))
|
||||||
.andExpect(status().isOk());
|
.andExpect(status().isOk());
|
||||||
|
|||||||
Reference in New Issue
Block a user