Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
159e634ffc |
@@ -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/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/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.
|
||||
|
||||
@@ -70,11 +70,6 @@ Use exactly these persistent branches unless the user changes the plan:
|
||||
| `work/attendance` | Policy, calendar, attendance, corrections, leave, metrics |
|
||||
| `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.
|
||||
|
||||
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.
|
||||
- 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.
|
||||
- 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.
|
||||
|
||||
@@ -62,14 +62,6 @@ npm ci
|
||||
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
|
||||
|
||||
### 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.
|
||||
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.
|
||||
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
|
||||
|
||||
|
||||
@@ -126,12 +126,6 @@ companion record under [`docs/tests`](docs/tests/README.md).
|
||||
| `work/attendance` | Policy, calendar, attendance workflows |
|
||||
| `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
|
||||
strict RED-to-GREEN TDD, add Javadoc during implementation, and update the
|
||||
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
|
||||
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
|
||||
|
||||
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,70 @@
|
||||
# Test Evidence: dark icon sprite presentation
|
||||
|
||||
- **Test type:** Web
|
||||
- **Requirement IDs:** `UI-006`, `UI-009`, `UI-010`, `UI-018`
|
||||
- **Scenario IDs:** `AC-UI-003`, `AC-UI-005`
|
||||
- **Test class/method:** `com.lab.labtimesheet.ui.UiContractWebTest#generatedLucideSymbolsRetainCurrentColorStrokePresentation`
|
||||
- **Implementation commit:** `pending`
|
||||
|
||||
## Protected behavior
|
||||
|
||||
Every local Lucide sprite symbol retains the source presentation attributes so icons referenced with `<use>` inherit `currentColor` rather than rendering with the SVG default black fill on dark surfaces.
|
||||
|
||||
## Test method
|
||||
|
||||
The focused web contract reads the generated classpath sprite, scans every emitted `<symbol>`, and checks the five presentation attributes on each symbol. It checks the deployable generated artifact rather than generator source text.
|
||||
|
||||
## Hand-derived expected result
|
||||
|
||||
Lucide 1.27.0 line icons use `fill="none"`, `stroke="currentColor"`, `stroke-width="2"`, `stroke-linecap="round"`, and `stroke-linejoin="round"` on their SVG root. Each selected generated symbol must preserve those values.
|
||||
|
||||
## RED
|
||||
|
||||
**Command**
|
||||
|
||||
```text
|
||||
env JAVA_HOME=/opt/homebrew/opt/openjdk@25 PATH=/opt/homebrew/opt/openjdk@25/bin:$PATH ./mvnw '-Dtest=UiContractWebTest#generatedLucideSymbolsRetainCurrentColorStrokePresentation' test
|
||||
```
|
||||
|
||||
**Observed result**
|
||||
|
||||
```text
|
||||
UiContractWebTest.generatedLucideSymbolsRetainCurrentColorStrokePresentation
|
||||
Missing fill on id="bell" viewBox="0 0 24 24" ==> expected: <true> but was: <false>
|
||||
Tests run: 1, Failures: 1, Errors: 0, Skipped: 0
|
||||
BUILD FAILURE
|
||||
```
|
||||
|
||||
## GREEN
|
||||
|
||||
**Command**
|
||||
|
||||
```text
|
||||
env PATH=/opt/homebrew/opt/node@24/bin:$PATH npm ci
|
||||
env PATH=/opt/homebrew/opt/node@24/bin:$PATH npm run build
|
||||
env JAVA_HOME=/opt/homebrew/opt/openjdk@25 PATH=/opt/homebrew/opt/openjdk@25/bin:$PATH ./mvnw '-Dtest=UiContractWebTest#generatedLucideSymbolsRetainCurrentColorStrokePresentation' test
|
||||
```
|
||||
|
||||
**Observed result**
|
||||
|
||||
```text
|
||||
Node v24.19.0 and npm 11.17.0 installed the locked dependencies.
|
||||
Tailwind CSS v4.3.3 rebuilt app.css and build-icons regenerated icons.svg.
|
||||
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
|
||||
BUILD SUCCESS
|
||||
```
|
||||
|
||||
## Affected suite
|
||||
|
||||
**Command and result**
|
||||
|
||||
```text
|
||||
env JAVA_HOME=/opt/homebrew/opt/openjdk@25 PATH=/opt/homebrew/opt/openjdk@25/bin:$PATH ./mvnw '-Dtest=UiContractWebTest' test
|
||||
|
||||
Tests run: 7, Failures: 0, Errors: 0, Skipped: 0
|
||||
BUILD SUCCESS
|
||||
```
|
||||
|
||||
## External-test boundaries
|
||||
|
||||
The deterministic asset contract proves the generated sprite carries theme-aware Lucide presentation attributes. It does not replace the taskmaster-owned integrated browser/detector pass for rendered layout and interactive states.
|
||||
@@ -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`);
|
||||
}
|
||||
@@ -10,10 +10,14 @@ const names = [
|
||||
const output = resolve('src/main/resources/static/assets/icons.svg');
|
||||
const symbols = await Promise.all(names.map(async (name) => {
|
||||
const svg = await readFile(resolve(`node_modules/lucide-static/icons/${name}.svg`), 'utf8');
|
||||
const viewBox = svg.match(/viewBox="([^"]+)"/)?.[1] ?? '0 0 24 24';
|
||||
const root = svg.match(/<svg\b([^>]*)>/)?.[1];
|
||||
const viewBox = root?.match(/viewBox="([^"]+)"/)?.[1] ?? '0 0 24 24';
|
||||
const presentation = ['fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin']
|
||||
.map((attribute) => root?.match(new RegExp(`${attribute}="[^"]+"`))?.[0])
|
||||
.join(' ');
|
||||
const body = svg.match(/<svg[\s\S]*?>([\s\S]*?)<\/svg>/)?.[1];
|
||||
if (!body) throw new Error(`Invalid Lucide SVG: ${name}`);
|
||||
return `<symbol id="${name}" viewBox="${viewBox}">${body.trim()}</symbol>`;
|
||||
return `<symbol id="${name}" viewBox="${viewBox}" ${presentation}>${body.trim()}</symbol>`;
|
||||
}));
|
||||
|
||||
await mkdir(dirname(output), { recursive: true });
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="display:none"><symbol id="bell" viewBox="0 0 24 24"><path d="M10.268 21a2 2 0 0 0 3.464 0" />
|
||||
<path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326" /></symbol><symbol id="calendar-days" viewBox="0 0 24 24"><path d="M8 2v4" />
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="display:none"><symbol id="bell" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.268 21a2 2 0 0 0 3.464 0" />
|
||||
<path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326" /></symbol><symbol id="calendar-days" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 2v4" />
|
||||
<path d="M16 2v4" />
|
||||
<rect width="18" height="18" x="3" y="4" rx="2" />
|
||||
<path d="M3 10h18" />
|
||||
@@ -8,27 +8,27 @@
|
||||
<path d="M16 14h.01" />
|
||||
<path d="M8 18h.01" />
|
||||
<path d="M12 18h.01" />
|
||||
<path d="M16 18h.01" /></symbol><symbol id="check-circle-2" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" />
|
||||
<path d="m9 12 2 2 4-4" /></symbol><symbol id="chevron-left" viewBox="0 0 24 24"><path d="m15 18-6-6 6-6" /></symbol><symbol id="chevron-right" viewBox="0 0 24 24"><path d="m9 18 6-6-6-6" /></symbol><symbol id="circle-user-round" viewBox="0 0 24 24"><path d="M17.925 20.056a6 6 0 0 0-11.851.001" />
|
||||
<path d="M16 18h.01" /></symbol><symbol id="check-circle-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" />
|
||||
<path d="m9 12 2 2 4-4" /></symbol><symbol id="chevron-left" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6" /></symbol><symbol id="chevron-right" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6" /></symbol><symbol id="circle-user-round" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.925 20.056a6 6 0 0 0-11.851.001" />
|
||||
<circle cx="12" cy="11" r="4" />
|
||||
<circle cx="12" cy="12" r="10" /></symbol><symbol id="clock" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" />
|
||||
<path d="M12 6v6l4 2" /></symbol><symbol id="folder-kanban" viewBox="0 0 24 24"><path d="M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13c0 1.1.9 2 2 2Z" />
|
||||
<circle cx="12" cy="12" r="10" /></symbol><symbol id="clock" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" />
|
||||
<path d="M12 6v6l4 2" /></symbol><symbol id="folder-kanban" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13c0 1.1.9 2 2 2Z" />
|
||||
<path d="M8 10v4" />
|
||||
<path d="M12 10v2" />
|
||||
<path d="M16 10v6" /></symbol><symbol id="folder-open" viewBox="0 0 24 24"><path d="m6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2" /></symbol><symbol id="inbox" viewBox="0 0 24 24"><polyline points="22 12 16 12 14 15 10 15 8 12 2 12" />
|
||||
<path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" /></symbol><symbol id="layout-dashboard" viewBox="0 0 24 24"><rect width="7" height="9" x="3" y="3" rx="1" />
|
||||
<path d="M16 10v6" /></symbol><symbol id="folder-open" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2" /></symbol><symbol id="inbox" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 16 12 14 15 10 15 8 12 2 12" />
|
||||
<path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" /></symbol><symbol id="layout-dashboard" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="7" height="9" x="3" y="3" rx="1" />
|
||||
<rect width="7" height="5" x="14" y="3" rx="1" />
|
||||
<rect width="7" height="9" x="14" y="12" rx="1" />
|
||||
<rect width="7" height="5" x="3" y="16" rx="1" /></symbol><symbol id="list-check" viewBox="0 0 24 24"><path d="M16 5H3" />
|
||||
<rect width="7" height="5" x="3" y="16" rx="1" /></symbol><symbol id="list-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 5H3" />
|
||||
<path d="M16 12H3" />
|
||||
<path d="M11 19H3" />
|
||||
<path d="m15 18 2 2 4-4" /></symbol><symbol id="log-out" viewBox="0 0 24 24"><path d="m16 17 5-5-5-5" />
|
||||
<path d="m15 18 2 2 4-4" /></symbol><symbol id="log-out" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m16 17 5-5-5-5" />
|
||||
<path d="M21 12H9" />
|
||||
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" /></symbol><symbol id="monitor" viewBox="0 0 24 24"><rect width="20" height="14" x="2" y="3" rx="2" />
|
||||
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" /></symbol><symbol id="monitor" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="14" x="2" y="3" rx="2" />
|
||||
<line x1="8" x2="16" y1="21" y2="21" />
|
||||
<line x1="12" x2="12" y1="17" y2="21" /></symbol><symbol id="moon" viewBox="0 0 24 24"><path d="M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401" /></symbol><symbol id="panel-left" viewBox="0 0 24 24"><rect width="18" height="18" x="3" y="3" rx="2" />
|
||||
<path d="M9 3v18" /></symbol><symbol id="settings" viewBox="0 0 24 24"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" />
|
||||
<circle cx="12" cy="12" r="3" /></symbol><symbol id="sun" viewBox="0 0 24 24"><circle cx="12" cy="12" r="4" />
|
||||
<line x1="12" x2="12" y1="17" y2="21" /></symbol><symbol id="moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401" /></symbol><symbol id="panel-left" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" />
|
||||
<path d="M9 3v18" /></symbol><symbol id="settings" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" />
|
||||
<circle cx="12" cy="12" r="3" /></symbol><symbol id="sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4" />
|
||||
<path d="M12 2v2" />
|
||||
<path d="M12 20v2" />
|
||||
<path d="m4.93 4.93 1.41 1.41" />
|
||||
@@ -36,10 +36,10 @@
|
||||
<path d="M2 12h2" />
|
||||
<path d="M20 12h2" />
|
||||
<path d="m6.34 17.66-1.41 1.41" />
|
||||
<path d="m19.07 4.93-1.41 1.41" /></symbol><symbol id="triangle-alert" viewBox="0 0 24 24"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" />
|
||||
<path d="m19.07 4.93-1.41 1.41" /></symbol><symbol id="triangle-alert" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" />
|
||||
<path d="M12 9v4" />
|
||||
<path d="M12 17h.01" /></symbol><symbol id="users" viewBox="0 0 24 24"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" />
|
||||
<path d="M12 17h.01" /></symbol><symbol id="users" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" />
|
||||
<path d="M16 3.128a4 4 0 0 1 0 7.744" />
|
||||
<path d="M22 21v-2a4 4 0 0 0-3-3.87" />
|
||||
<circle cx="9" cy="7" r="4" /></symbol><symbol id="x" viewBox="0 0 24 24"><path d="M18 6 6 18" />
|
||||
<circle cx="9" cy="7" r="4" /></symbol><symbol id="x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18" />
|
||||
<path d="m6 6 12 12" /></symbol></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 6.1 KiB |
@@ -97,6 +97,26 @@ class UiContractWebTest {
|
||||
assertTrue(themeBootstrap.contains("matchMedia('(prefers-color-scheme: dark)')"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void generatedLucideSymbolsRetainCurrentColorStrokePresentation() throws Exception {
|
||||
String icons = new ClassPathResource("static/assets/icons.svg")
|
||||
.getContentAsString(StandardCharsets.UTF_8);
|
||||
Matcher symbols = Pattern.compile("<symbol\\b([^>]*)>").matcher(icons);
|
||||
int symbolCount = 0;
|
||||
|
||||
while (symbols.find()) {
|
||||
String attributes = symbols.group(1);
|
||||
assertTrue(attributes.contains("fill=\"none\""), () -> "Missing fill on " + attributes);
|
||||
assertTrue(attributes.contains("stroke=\"currentColor\""), () -> "Missing stroke on " + attributes);
|
||||
assertTrue(attributes.contains("stroke-width=\"2\""), () -> "Missing stroke width on " + attributes);
|
||||
assertTrue(attributes.contains("stroke-linecap=\"round\""), () -> "Missing stroke linecap on " + attributes);
|
||||
assertTrue(attributes.contains("stroke-linejoin=\"round\""), () -> "Missing stroke linejoin on " + attributes);
|
||||
symbolCount++;
|
||||
}
|
||||
|
||||
assertTrue(symbolCount > 0, "The generated sprite must contain symbols");
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(username = "admin@example.test", roles = "ADMIN")
|
||||
void collapsedSidebarExposesStateAndKeyboardVisibleControlNames() throws Exception {
|
||||
|
||||
Reference in New Issue
Block a user