Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
408f121
docs: design for an in-page help system on the planning page
renemadsen Sep 4, 2026
0642f02
docs: add copy rules — help text never references admin capabilities
renemadsen Sep 4, 2026
d6b63cb
docs: add help search and task entries to the planning help design
renemadsen Sep 4, 2026
d529131
docs: add reviewed implementation plan for the planning help system
renemadsen Sep 4, 2026
25455d8
docs: fix plan ordering hazard — panel emits replay output instead of…
renemadsen Sep 4, 2026
aaf208e
chore: ignore the local node_modules symlink used for running plugin …
renemadsen Sep 4, 2026
80fbc97
feat(help): add planning help registry, types and English content
renemadsen Sep 4, 2026
8d9f554
fix(help): state day-type mutual exclusivity in the day-off task
renemadsen Sep 4, 2026
6acb1a1
feat(help): resolve help prose by locale with per-entry English fallback
renemadsen Sep 4, 2026
e11184f
feat(help): add Danish help content and search keywords
renemadsen Sep 4, 2026
ab543bb
docs: correct the day-flag hazard — it is naming, not adjacency
renemadsen Sep 4, 2026
d9c4883
fix(help): correct the day-type warning in both locales
renemadsen Sep 4, 2026
72362eb
test(help): make the day-type warning assertion non-vacuous
renemadsen Sep 4, 2026
f7242f6
feat(help): add diacritic-folding help search with tasks ranked first
renemadsen Sep 4, 2026
f5679a5
fix(help-search): replace vacuous diacritic folding tests with real k…
renemadsen Sep 4, 2026
81e722b
fix(help-search): replace vacuous å assertion with real keyword 'frat…
renemadsen Sep 4, 2026
f501426
docs: correct the spec's own vacuous diacritic-folding example
renemadsen Sep 4, 2026
32fe93a
feat(help): add tp-help-icon popover built on cdkConnectedOverlay
renemadsen Sep 4, 2026
7de16b4
fix(help): dismiss the help popover without a backdrop, and label it …
renemadsen Sep 4, 2026
20aa0cb
docs: the help popover drops its backdrop
renemadsen Sep 4, 2026
e67d985
fix(help): dismiss the help popover on scroll instead of following th…
renemadsen Sep 4, 2026
706e1f5
feat(help): add tp-help-hint inline hint component
renemadsen Sep 4, 2026
698dfec
fix(help): add icon-text assertion to help-hint spec
renemadsen Sep 4, 2026
16b7661
feat(help): add searchable help side panel
renemadsen Sep 4, 2026
eb12f85
fix(help): name the no-match query, label the kind badge, use the sur…
renemadsen Sep 4, 2026
0e1edc2
feat(help): add guided tour that skips steps with no anchor
renemadsen Sep 4, 2026
32f4cf0
fix(help): scope the tour card to its own tour and end it when its an…
renemadsen Sep 4, 2026
281e292
docs: tp-help-tour takes [tour] only, not isAdmin
renemadsen Sep 4, 2026
374a8a9
fix(help): do not mark a tour seen when the page ends it, only when t…
renemadsen Sep 4, 2026
9ec3c3a
docs: Task 9 must use abort(), not stop(), when tearing down the dial…
renemadsen Sep 4, 2026
da8425f
feat(help): wire help icon, panel, tours and hints into the planning …
renemadsen Sep 4, 2026
819266d
fix(help): restore field widths, park the panel in the overlay contai…
renemadsen Sep 4, 2026
2e1bcef
fix(help): raise the panel above the CDK overlay layer it is parked in
renemadsen Sep 4, 2026
25d231f
fix(help): seed the tour flag for Playwright and close the whole-bran…
renemadsen Sep 4, 2026
3a91a86
fix(help): keep the dialog surface when a related link is followed
renemadsen Sep 4, 2026
e79f55e
feat(help): gate the whole help system on admin, and unbanner the wor…
renemadsen Sep 5, 2026
5030529
fix(help): gate the ? button on the live flag, and drop a vacuous ass…
renemadsen Sep 5, 2026
07507ad
docs: record the admin-only rollout of the help system
renemadsen Sep 5, 2026
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 .github/workflows/dotnet-core-master.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ jobs:
- name: Copy dependencies
run: |
cp -av eform-angular-timeplanning-plugin/eform-client/src/app/plugins/modules/time-planning-pn eform-angular-frontend/eform-client/src/app/plugins/modules/time-planning-pn
# The unit tests include a guard on the Playwright tour seed, which lives
# outside the plugin src tree, so bring it and the config it is wired into.
mkdir -p eform-angular-frontend/eform-client/playwright
rm -rf eform-angular-frontend/eform-client/playwright/helpers
cp -av eform-angular-timeplanning-plugin/eform-client/playwright/helpers eform-angular-frontend/eform-client/playwright/helpers
cp -av eform-angular-timeplanning-plugin/eform-client/playwright.config.ts eform-angular-frontend/eform-client/playwright.config.ts
cd eform-angular-frontend/eform-client && ../../eform-angular-timeplanning-plugin/testinginstallpn.sh
- name: yarn install
run: cd eform-angular-frontend/eform-client && yarn install
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/dotnet-core-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ jobs:
- name: Copy dependencies
run: |
cp -av eform-angular-timeplanning-plugin/eform-client/src/app/plugins/modules/time-planning-pn eform-angular-frontend/eform-client/src/app/plugins/modules/time-planning-pn
# The unit tests include a guard on the Playwright tour seed, which lives
# outside the plugin src tree, so bring it and the config it is wired into.
mkdir -p eform-angular-frontend/eform-client/playwright
rm -rf eform-angular-frontend/eform-client/playwright/helpers
cp -av eform-angular-timeplanning-plugin/eform-client/playwright/helpers eform-angular-frontend/eform-client/playwright/helpers
cp -av eform-angular-timeplanning-plugin/eform-client/playwright.config.ts eform-angular-frontend/eform-client/playwright.config.ts
cd eform-angular-frontend/eform-client && ../../eform-angular-timeplanning-plugin/testinginstallpn.sh
- name: yarn install
run: cd eform-angular-frontend/eform-client && yarn install
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -357,3 +357,4 @@ eform-client/.idea/
.claude
.worktrees/
.superpowers/
eform-client/node_modules
2,685 changes: 2,685 additions & 0 deletions docs/superpowers/plans/2026-09-04-planning-help-system.md

Large diffs are not rendered by default.

394 changes: 394 additions & 0 deletions docs/superpowers/specs/2026-09-04-planning-help-system-design.md

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions eform-client/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ export default defineConfig({
retries: 0,
use: {
baseURL: 'http://localhost:4200',
// Seeds localStorage so the planning page's onboarding tours count as already
// seen. Playwright gives every test a fresh context with empty storage, so
// without this both tours auto-start: the page tour drops a card over the top
// grid rows and the dialog tour drops one over the shift-1 fields, and
// Playwright's actionability check then fails on the intercepting overlay for
// every spec that clicks a day cell or #saveButton. The seed matches
// TOUR_STORAGE_KEY in help/services/help-tour.service.ts; a jest test in the
// plugin (help/playwright-tour-seed.spec.ts) fails if the two drift apart.
storageState: 'playwright/helpers/tour-seen.storage.json',
viewport: { width: 1920, height: 1080 },
video: 'on',
screenshot: 'only-on-failure',
Expand Down
14 changes: 14 additions & 0 deletions eform-client/playwright/helpers/tour-seen.storage.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"cookies": [],
"origins": [
{
"origin": "http://localhost:4200",
"localStorage": [
{
"name": "tp.planning.tour.v1",
"value": "[\"page\",\"dialog\"]"
}
]
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
<small class="microting-uid" matTooltip="Id">({{ data.planningPrDayModels.id }})</small></div>
<button
mat-icon-button
data-tp-help="dayCell.versionHistory"
(click)="openVersionHistory()"
matTooltip="{{'View history' | translate}}">
<mat-icon>history</mat-icon>
</button>
<tp-help-icon helpId="dayCell.versionHistory" (openInPanel)="openHelp($event)"></tp-help-icon>
</div>

<div class="dialog-site-tags" *ngIf="data.tags?.length">
Expand All @@ -17,9 +19,10 @@

<div mat-dialog-content>
<div class="workday-dialog-container">
<div class="main-content" [class.with-side-panel]="selectedGpsCoordinate || selectedSnapshot">
<div class="main-content" cdkScrollable [class.with-side-panel]="selectedGpsCoordinate || selectedSnapshot">
<br/>
<mtx-grid
data-tp-help="dayCell.shiftCount"
[columns]="tableHeaders"
[data]="shiftData"
[showPaginator]="false"
Expand All @@ -30,13 +33,17 @@

<ng-template #plannedShiftTemplate let-shiftId="shiftId">
<ng-container [formGroupName]="'planned'">
<div [formGroupName]="'shift' + shiftId">
<div [formGroupName]="'shift' + shiftId" data-tp-help="dayCell.plannedTimes">
<tp-help-icon
*ngIf="shiftId === '1'"
helpId="dayCell.plannedTimes"
(openInPanel)="openHelp($event)"></tp-help-icon>
<mat-error *ngFor="let error of getInputErrors('planned.shift' + shiftId)">
<small>{{ error }}</small>
</mat-error>

<div class="flex-row">
<mat-form-field>
<mat-form-field data-tp-help="dayCell.oneMinuteIntervals">
<mat-label>{{ 'Start' | translate }}</mat-label>
<input
matInput
Expand All @@ -54,7 +61,7 @@
(closed)="calculatePlanHours()">
</ngx-material-timepicker>
</mat-form-field>
<button mat-icon-button color="warn" (click)="resetPlannedTimes((+shiftId - 1) * 3 + 1)">
<button mat-icon-button color="warn" data-tp-help="dayCell.resetField" (click)="resetPlannedTimes((+shiftId - 1) * 3 + 1)">
<mat-icon>delete</mat-icon>
</button>
</div>
Expand Down Expand Up @@ -142,13 +149,15 @@
<button
*ngIf="hasGpsData(key)"
mat-icon-button
data-tp-help="dayCell.gps"
color="primary"
(click)="onGpsClick(key)"
matTooltip="{{'View GPS Location' | translate}}">
<mat-icon>location_on</mat-icon>
</button>
<button *ngIf="hasSnapshotData(key)"
mat-icon-button
data-tp-help="dayCell.snapshot"
color="primary"
(click)="onSnapshotClick(key)"
matTooltip="{{'View Snapshot' | translate}}">
Expand All @@ -163,7 +172,11 @@
let-pauseKey="pauseKey"
let-stopKey="stopKey">
<ng-container [formGroupName]="'actual'">
<div [formGroupName]="'shift' + shiftId">
<div [formGroupName]="'shift' + shiftId" data-tp-help="dayCell.actualTimes">
<tp-help-icon
*ngIf="shiftId === '1'"
helpId="dayCell.actualTimes"
(openInPanel)="openHelp($event)"></tp-help-icon>
<mat-error *ngFor="let error of getInputErrors('actual.shift' + shiftId)">
<small>{{ error }}</small>
</mat-error>
Expand Down Expand Up @@ -232,11 +245,16 @@
mat-icon-button
type="button"
color="primary"
data-tp-help="dayCell.resetPauseToRecorded"
[matTooltip]="'Reset pause to recorded' | translate"
[attr.data-testid]="'resetPauseToRecorded' + shiftId"
(click)="resetPauseToRecorded(+shiftId)">
<mat-icon>restore</mat-icon>
</button>
<tp-help-icon
*ngIf="shiftId === '1'"
helpId="dayCell.resetPauseToRecorded"
(openInPanel)="openHelp($event)"></tp-help-icon>
<ng-container
[ngTemplateOutlet]="trackingButtons"
[ngTemplateOutletContext]="{ key: pauseKey }">
Expand Down Expand Up @@ -316,18 +334,26 @@
[value]="data.planningPrDayModels.sumFlexStart.toFixed(2)"/>
</mat-form-field>

<mat-form-field>
<mat-label>{{ 'Plan hours' | translate }}</mat-label>
<input
matInput
type="number"
id="planHours"
formControlName="planHours"
(change)="calculatePlanHours()"/>
<mat-error data-testid="planHours-Error" *ngIf="workdayForm.hasError('tooManyHours')">
Total planned hours cannot exceed 24
</mat-error>
</mat-form-field>
<div class="field-with-help">
<mat-form-field data-tp-help="dayCell.planHours">
<mat-label>{{ 'Plan hours' | translate }}</mat-label>
<input
matInput
type="number"
id="planHours"
formControlName="planHours"
(change)="calculatePlanHours()"/>
<mat-error data-testid="planHours-Error" *ngIf="workdayForm.hasError('tooManyHours')">
Total planned hours cannot exceed 24
</mat-error>
</mat-form-field>
<tp-help-icon helpId="dayCell.planHours" (openInPanel)="openHelp($event)"></tp-help-icon>
</div>
<tp-help-hint
*ngIf="workdayForm.hasError('tooManyHours')"
helpId="dayCell.planHoursLimit"
data-tp-help="dayCell.planHoursLimit"
tone="warn"></tp-help-hint>

<mat-form-field *ngIf="!isInTheFuture">
<mat-label>{{ 'NettoHours' | translate }}</mat-label>
Expand All @@ -340,56 +366,73 @@
[value]="data.planningPrDayModels.actualHours.toFixed(2)"/>
</mat-form-field>

<mat-form-field *ngIf="!isInTheFuture && data.planningPrDayModels.nettoHoursOverrideActive">
<mat-label>{{ 'NettoHours override' | translate }}</mat-label>
<input
matInput
type="number"
id="nettoHoursOverride"
formControlName="nettoHoursOverride"
(change)="calculatePlanHours()"/>
</mat-form-field>
<div class="field-with-help" *ngIf="!isInTheFuture && data.planningPrDayModels.nettoHoursOverrideActive">
<mat-form-field data-tp-help="dayCell.nettoOverride">
<mat-label>{{ 'NettoHours override' | translate }}</mat-label>
<input
matInput
type="number"
id="nettoHoursOverride"
formControlName="nettoHoursOverride"
(change)="calculatePlanHours()"/>
</mat-form-field>
<tp-help-icon helpId="dayCell.nettoOverride" (openInPanel)="openHelp($event)"></tp-help-icon>
</div>

<br *ngIf="data.planningPrDayModels.nettoHoursOverrideActive"/>
<mat-form-field *ngIf="!isInTheFuture">
<mat-label>{{ 'Flex' | translate }}</mat-label>
<input
matInput
type="text"
id="todaysFlex"
readonly
disabled
[value]="todaysFlex.toFixed(2)"/>
</mat-form-field>
<div class="field-with-help" *ngIf="!isInTheFuture">
<mat-form-field data-tp-help="flex.whatIsFlex">
<mat-label>{{ 'Flex' | translate }}</mat-label>
<input
matInput
type="text"
id="todaysFlex"
readonly
disabled
[value]="todaysFlex.toFixed(2)"/>
</mat-form-field>
<tp-help-icon helpId="flex.whatIsFlex" (openInPanel)="openHelp($event)"></tp-help-icon>
</div>

<mat-form-field *ngIf="!isInTheFuture">
<mat-label>{{ 'PaidOutFlex' | translate }}</mat-label>
<input
matInput
type="text"
id="paidOutFlex"
formControlName="paidOutFlex"
(change)="calculatePlanHours()"/>
</mat-form-field>
<div class="field-with-help" *ngIf="!isInTheFuture">
<mat-form-field data-tp-help="dayCell.paidOutFlex">
<mat-label>{{ 'PaidOutFlex' | translate }}</mat-label>
<input
matInput
type="text"
id="paidOutFlex"
formControlName="paidOutFlex"
(change)="calculatePlanHours()"/>
</mat-form-field>
<tp-help-icon helpId="dayCell.paidOutFlex" (openInPanel)="openHelp($event)"></tp-help-icon>
<tp-help-icon
helpId="flex.paidOutFlexRelation"
data-tp-help="flex.paidOutFlexRelation"
(openInPanel)="openHelp($event)"></tp-help-icon>
</div>

<mat-form-field *ngIf="!isInTheFuture">
<mat-label>{{ 'Flex balance to date' | translate }}</mat-label>
<input
matInput
type="text"
id="flexIncludingToday"
readonly
disabled
[value]="data.planningPrDayModels.sumFlexEnd.toFixed(2)"/>
</mat-form-field>
<div class="field-with-help" *ngIf="!isInTheFuture">
<mat-form-field data-tp-help="flex.sumFlex">
<mat-label>{{ 'Flex balance to date' | translate }}</mat-label>
<input
matInput
type="text"
id="flexIncludingToday"
readonly
disabled
[value]="data.planningPrDayModels.sumFlexEnd.toFixed(2)"/>
</mat-form-field>
<tp-help-icon helpId="flex.sumFlex" (openInPanel)="openHelp($event)"></tp-help-icon>
</div>

<p *ngIf="data.planningPrDayModels.workerComment">
<strong>{{ 'CommentWorker' | translate }}:</strong>
{{ data.planningPrDayModels.workerComment }}
</p>
</div>

<div id="flags" formGroupName="flags" class="d-flex flex-column gap-4">
<tp-help-icon helpId="dayCell.flags" (openInPanel)="openHelp($event)"></tp-help-icon>
<div id="flags" data-tp-help="dayCell.flags" formGroupName="flags" class="d-flex flex-column gap-4">
<ng-container *ngFor="let key of enumKeys">
<ng-container *ngIf="key !== 'Blank' && key !== 'Care'">
<mat-checkbox
Expand All @@ -403,14 +446,23 @@
</div>

<br/><br/>
<mat-form-field>
<mat-label>{{ 'CommentOffice' | translate }}</mat-label>
<input
matInput
type="text"
id="CommentOffice"
formControlName="commentOffice"/>
</mat-form-field>
<div class="flex-row">
<mat-form-field data-tp-help="dayCell.commentOffice">
<mat-label>{{ 'CommentOffice' | translate }}</mat-label>
<input
matInput
type="text"
id="CommentOffice"
formControlName="commentOffice"/>
</mat-form-field>
<tp-help-icon helpId="dayCell.commentOffice" (openInPanel)="openHelp($event)"></tp-help-icon>
</div>

<tp-help-hint
*ngIf="isInTheFuture"
helpId="dayCell.futureDisabled"
data-tp-help="dayCell.futureDisabled"
tone="warn"></tp-help-hint>

<br/>
</div>
Expand Down Expand Up @@ -448,9 +500,11 @@
{{ 'Cancel' | translate }}
</button>

<tp-help-icon helpId="dayCell.save" (openInPanel)="openHelp($event)"></tp-help-icon>
<button
class="btn-primary btn-primary--icon-left"
id="saveButton"
data-tp-help="dayCell.save"
(click)="onUpdateWorkDayEntity()"
[disabled]="workdayForm.invalid"
[mat-dialog-close]="data"
Expand All @@ -459,3 +513,5 @@
</button>
</div>
</form>

<tp-help-tour tour="dialog"></tp-help-tour>
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,20 @@
gap: 16px; /* Adjust spacing between elements */
}

/* A form field paired with its help icon. These fields were direct children of
the .d-flex.flex-column column, where a flex item stretches to the column
width; putting them in a row would otherwise shrink them to mat-form-field's
intrinsic width and leave the un-paired fields beside them looking ragged. */
.field-with-help {
display: flex;
align-items: center;
gap: 8px;

mat-form-field {
flex: 1 1 auto;
}
}

.workday-dialog-container {
display: flex;
gap: 20px;
Expand Down
Loading
Loading