Keep the inbox tour on screen and in step with the user #298

Merged
robbertbos merged 1 commit from tour-waits-for-async-banners into main 2026-08-13 09:23:22 +00:00
Owner

Closes #289, plus three defects found while testing that fix.

A coach-mark is a top-layer bubble anchored to a live element, which is where all four problems come from.

It started too early. The banners App.vue renders above the router view hang off /api/auth/me and /api/accounts; arriving late, they dragged an open bubble down the page (#289). The tour now waits for both. The retention banner needed nothing - it lands with the settings query tour.active already waits for.

A click beside a callout ended it for good. NLDD reports that as reason ignored, and Waggle mapped it to ending the tour: hint marked seen, demo card gone with it, nothing left on screen to explain what happened. It now puts the same step back and leaves the counter alone. Advancing on it is wrong the other way round - every stray click walks the tour forward, and step 4 opens a card modal over whatever the user was doing.

One click counted as many steps. A card list mounts a coach-mark per row (25 cards = 50 of them, all inactive but the tour's own) and NLDD fires nldd-close on those too. The tour counted each one, raced to the end and dismissed itself. Hint now stays quiet unless it is the active coach-mark - the right rule for every consumer. Steps 1-3 live in the list and 4-7 in the card, so the tour also follows the user across that line instead of leaving the screen blank with the skip button still up.

The in-card steps were painted under the modal. Both live in the top layer, where the last promotion wins, so a step lit before showModal() sat under the dialog - present, visible to every locator, invisible to the user until a Tab re-promoted it. Modal already emits open after showModal(), so that is when they light up.

Testing

  • Inbox.test.ts, Hint.test.ts: the tour is held while me/accounts are pending; a click beside the callout keeps its step; a close outside a running tour does nothing; the tour follows the user in and out of a card; an inactive hint stays quiet.
  • onboarding-tour-start.spec.ts: delays /api/accounts and asserts the banner is already up when the tour starts; asserts a click beside the callout neither ends nor advances it; asserts the in-card callout is the topmost element via elementFromPoint - toBeVisible() passes either way and would not catch it.
  • Each was confirmed red against the behaviour it replaces.
  • attachments-lightbox.spec.ts stubbed GET /api/settings without hints_seen, so the tour ran through that whole file and competed for the focus those tests assert on; it only passed because the old code destroyed the tour on the first stray click. The stub now carries hints_seen.

Verified in the browser as well as in CI. Local gates: 1095 vitest, e2e 98 passed / 3 skipped, typecheck, eslint, pre-commit.

Closes #289, plus three defects found while testing that fix. A coach-mark is a top-layer bubble anchored to a live element, which is where all four problems come from. **It started too early.** The banners `App.vue` renders above the router view hang off `/api/auth/me` and `/api/accounts`; arriving late, they dragged an open bubble down the page (#289). The tour now waits for both. The retention banner needed nothing - it lands with the settings query `tour.active` already waits for. **A click beside a callout ended it for good.** NLDD reports that as reason `ignored`, and Waggle mapped it to ending the tour: hint marked seen, demo card gone with it, nothing left on screen to explain what happened. It now puts the same step back and leaves the counter alone. Advancing on it is wrong the other way round - every stray click walks the tour forward, and step 4 opens a card modal over whatever the user was doing. **One click counted as many steps.** A card list mounts a coach-mark per row (25 cards = 50 of them, all inactive but the tour's own) and NLDD fires `nldd-close` on those too. The tour counted each one, raced to the end and dismissed itself. `Hint` now stays quiet unless it is the active coach-mark - the right rule for every consumer. Steps 1-3 live in the list and 4-7 in the card, so the tour also follows the user across that line instead of leaving the screen blank with the skip button still up. **The in-card steps were painted under the modal.** Both live in the top layer, where the last promotion wins, so a step lit before `showModal()` sat under the dialog - present, visible to every locator, invisible to the user until a Tab re-promoted it. `Modal` already emits `open` after `showModal()`, so that is when they light up. ### Testing - `Inbox.test.ts`, `Hint.test.ts`: the tour is held while `me`/`accounts` are pending; a click beside the callout keeps its step; a close outside a running tour does nothing; the tour follows the user in and out of a card; an inactive hint stays quiet. - `onboarding-tour-start.spec.ts`: delays `/api/accounts` and asserts the banner is already up when the tour starts; asserts a click beside the callout neither ends nor advances it; asserts the in-card callout is the topmost element via `elementFromPoint` - `toBeVisible()` passes either way and would not catch it. - Each was confirmed red against the behaviour it replaces. - `attachments-lightbox.spec.ts` stubbed `GET /api/settings` without `hints_seen`, so the tour ran through that whole file and competed for the focus those tests assert on; it only passed because the old code destroyed the tour on the first stray click. The stub now carries `hints_seen`. Verified in the browser as well as in CI. Local gates: 1095 vitest, e2e 98 passed / 3 skipped, typecheck, eslint, pre-commit.
robbertbos force-pushed tour-waits-for-async-banners from 65d073147b
Some checks failed
CI / release-scripts (pull_request) Successful in 5s
security-scan / SBOM (trivy) (pull_request) Successful in 9s
security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 11s
security-scan / JS SCA (npm audit) (pull_request) Successful in 13s
CI / pre-commit (pull_request) Failing after 14s
security-scan / Python SAST (bandit) (pull_request) Successful in 16s
CI / backend-test (pull_request) Failing after 30s
security-scan / Python SCA (pip-audit) (pull_request) Successful in 35s
test-build / build (frontend) (pull_request) Successful in 45s
test-build / build (backend) (pull_request) Successful in 46s
test-build / build (pull_request) Successful in 0s
CI / frontend-test (pull_request) Successful in 1m23s
CI / e2e (pull_request) Successful in 3m37s
to a88fbd23b5
Some checks failed
CI / release-scripts (pull_request) Successful in 6s
security-scan / SBOM (trivy) (pull_request) Successful in 8s
security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 11s
security-scan / JS SCA (npm audit) (pull_request) Successful in 13s
CI / pre-commit (pull_request) Failing after 16s
security-scan / Python SAST (bandit) (pull_request) Successful in 17s
security-scan / Python SCA (pip-audit) (pull_request) Successful in 36s
test-build / build (frontend) (pull_request) Successful in 45s
test-build / build (backend) (pull_request) Successful in 49s
test-build / build (pull_request) Successful in 0s
CI / frontend-test (pull_request) Successful in 1m6s
CI / backend-test (pull_request) Successful in 2m38s
CI / e2e (pull_request) Successful in 3m55s
2026-08-12 21:07:57 +00:00
Compare
robbertbos force-pushed tour-waits-for-async-banners from a88fbd23b5
Some checks failed
CI / release-scripts (pull_request) Successful in 6s
security-scan / SBOM (trivy) (pull_request) Successful in 8s
security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 11s
security-scan / JS SCA (npm audit) (pull_request) Successful in 13s
CI / pre-commit (pull_request) Failing after 16s
security-scan / Python SAST (bandit) (pull_request) Successful in 17s
security-scan / Python SCA (pip-audit) (pull_request) Successful in 36s
test-build / build (frontend) (pull_request) Successful in 45s
test-build / build (backend) (pull_request) Successful in 49s
test-build / build (pull_request) Successful in 0s
CI / frontend-test (pull_request) Successful in 1m6s
CI / backend-test (pull_request) Successful in 2m38s
CI / e2e (pull_request) Successful in 3m55s
to 4dd85de8be
Some checks failed
CI / release-scripts (pull_request) Successful in 10s
security-scan / SBOM (trivy) (pull_request) Successful in 13s
security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 20s
security-scan / JS SCA (npm audit) (pull_request) Successful in 22s
security-scan / Python SAST (bandit) (pull_request) Successful in 23s
security-scan / Python SCA (pip-audit) (pull_request) Successful in 43s
CI / backend-test (pull_request) Failing after 46s
test-build / build (backend) (pull_request) Successful in 1m11s
CI / pre-commit (pull_request) Successful in 1m11s
test-build / build (frontend) (pull_request) Successful in 1m3s
test-build / build (pull_request) Successful in 0s
CI / frontend-test (pull_request) Successful in 1m16s
CI / e2e (pull_request) Failing after 1m24s
2026-08-12 21:17:41 +00:00
Compare
robbertbos force-pushed tour-waits-for-async-banners from 4dd85de8be
Some checks failed
CI / release-scripts (pull_request) Successful in 10s
security-scan / SBOM (trivy) (pull_request) Successful in 13s
security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 20s
security-scan / JS SCA (npm audit) (pull_request) Successful in 22s
security-scan / Python SAST (bandit) (pull_request) Successful in 23s
security-scan / Python SCA (pip-audit) (pull_request) Successful in 43s
CI / backend-test (pull_request) Failing after 46s
test-build / build (backend) (pull_request) Successful in 1m11s
CI / pre-commit (pull_request) Successful in 1m11s
test-build / build (frontend) (pull_request) Successful in 1m3s
test-build / build (pull_request) Successful in 0s
CI / frontend-test (pull_request) Successful in 1m16s
CI / e2e (pull_request) Failing after 1m24s
to d0fb45944d
Some checks failed
security-scan / SBOM (trivy) (pull_request) Failing after 9s
CI / release-scripts (pull_request) Successful in 10s
security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 15s
security-scan / JS SCA (npm audit) (pull_request) Successful in 17s
CI / pre-commit (pull_request) Failing after 19s
security-scan / Python SAST (bandit) (pull_request) Successful in 20s
security-scan / Python SCA (pip-audit) (pull_request) Successful in 38s
CI / e2e (pull_request) Has been cancelled
test-build / build (pull_request) Has been cancelled
CI / backend-test (pull_request) Has been cancelled
CI / frontend-test (pull_request) Has been cancelled
test-build / build (frontend) (pull_request) Has been cancelled
test-build / build (backend) (pull_request) Has been cancelled
2026-08-12 21:21:54 +00:00
Compare
robbertbos force-pushed tour-waits-for-async-banners from d0fb45944d
Some checks failed
security-scan / SBOM (trivy) (pull_request) Failing after 9s
CI / release-scripts (pull_request) Successful in 10s
security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 15s
security-scan / JS SCA (npm audit) (pull_request) Successful in 17s
CI / pre-commit (pull_request) Failing after 19s
security-scan / Python SAST (bandit) (pull_request) Successful in 20s
security-scan / Python SCA (pip-audit) (pull_request) Successful in 38s
CI / e2e (pull_request) Has been cancelled
test-build / build (pull_request) Has been cancelled
CI / backend-test (pull_request) Has been cancelled
CI / frontend-test (pull_request) Has been cancelled
test-build / build (frontend) (pull_request) Has been cancelled
test-build / build (backend) (pull_request) Has been cancelled
to 6265a4cb82
Some checks failed
CI / release-scripts (pull_request) Successful in 8s
security-scan / SBOM (trivy) (pull_request) Successful in 11s
security-scan / JS SCA (npm audit) (pull_request) Successful in 17s
security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 17s
CI / pre-commit (pull_request) Failing after 19s
security-scan / Python SAST (bandit) (pull_request) Successful in 21s
security-scan / Python SCA (pip-audit) (pull_request) Successful in 39s
test-build / build (frontend) (pull_request) Successful in 52s
test-build / build (backend) (pull_request) Successful in 54s
test-build / build (pull_request) Successful in 0s
CI / frontend-test (pull_request) Successful in 1m0s
CI / backend-test (pull_request) Successful in 2m5s
CI / e2e (pull_request) Successful in 3m9s
2026-08-12 21:22:33 +00:00
Compare
robbertbos force-pushed tour-waits-for-async-banners from 6265a4cb82
Some checks failed
CI / release-scripts (pull_request) Successful in 8s
security-scan / SBOM (trivy) (pull_request) Successful in 11s
security-scan / JS SCA (npm audit) (pull_request) Successful in 17s
security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 17s
CI / pre-commit (pull_request) Failing after 19s
security-scan / Python SAST (bandit) (pull_request) Successful in 21s
security-scan / Python SCA (pip-audit) (pull_request) Successful in 39s
test-build / build (frontend) (pull_request) Successful in 52s
test-build / build (backend) (pull_request) Successful in 54s
test-build / build (pull_request) Successful in 0s
CI / frontend-test (pull_request) Successful in 1m0s
CI / backend-test (pull_request) Successful in 2m5s
CI / e2e (pull_request) Successful in 3m9s
to eb8f2a6a27
All checks were successful
CI / release-scripts (pull_request) Successful in 8s
security-scan / SBOM (trivy) (pull_request) Successful in 13s
security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 18s
security-scan / JS SCA (npm audit) (pull_request) Successful in 21s
security-scan / Python SAST (bandit) (pull_request) Successful in 23s
security-scan / Python SCA (pip-audit) (pull_request) Successful in 41s
test-build / build (frontend) (pull_request) Successful in 1m10s
test-build / build (backend) (pull_request) Successful in 1m13s
test-build / build (pull_request) Successful in 0s
CI / pre-commit (pull_request) Successful in 1m13s
CI / frontend-test (pull_request) Successful in 1m21s
CI / backend-test (pull_request) Successful in 2m54s
CI / e2e (pull_request) Successful in 3m58s
2026-08-12 21:33:00 +00:00
Compare
robbertbos force-pushed tour-waits-for-async-banners from 247b575ccf
Some checks failed
CI / release-scripts (pull_request) Successful in 4s
security-scan / Filesystem scan (trivy fs) (pull_request) Failing after 5s
security-scan / SBOM (trivy) (pull_request) Successful in 10s
security-scan / JS SCA (npm audit) (pull_request) Successful in 16s
security-scan / Python SAST (bandit) (pull_request) Successful in 17s
security-scan / Python SCA (pip-audit) (pull_request) Successful in 35s
test-build / build (frontend) (pull_request) Successful in 1m17s
test-build / build (backend) (pull_request) Successful in 1m21s
CI / pre-commit (pull_request) Successful in 1m23s
test-build / build (pull_request) Successful in 0s
CI / frontend-test (pull_request) Successful in 1m25s
CI / backend-test (pull_request) Successful in 3m35s
CI / e2e (pull_request) Successful in 5m4s
to 259656060f
All checks were successful
CI / release-scripts (pull_request) Successful in 9s
security-scan / SBOM (trivy) (pull_request) Successful in 11s
security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 19s
security-scan / JS SCA (npm audit) (pull_request) Successful in 21s
security-scan / Python SAST (bandit) (pull_request) Successful in 22s
security-scan / Python SCA (pip-audit) (pull_request) Successful in 45s
CI / pre-commit (pull_request) Successful in 1m16s
test-build / build (frontend) (pull_request) Successful in 1m13s
test-build / build (backend) (pull_request) Successful in 1m18s
test-build / build (pull_request) Successful in 0s
CI / frontend-test (pull_request) Successful in 1m29s
CI / backend-test (pull_request) Successful in 3m37s
CI / e2e (pull_request) Successful in 4m30s
2026-08-13 08:08:21 +00:00
Compare
robbertbos changed title from Start the inbox tour only once the banners above it have settled to Keep the inbox tour on screen and in step with the user 2026-08-13 08:08:51 +00:00
robbertbos force-pushed tour-waits-for-async-banners from 259656060f
All checks were successful
CI / release-scripts (pull_request) Successful in 9s
security-scan / SBOM (trivy) (pull_request) Successful in 11s
security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 19s
security-scan / JS SCA (npm audit) (pull_request) Successful in 21s
security-scan / Python SAST (bandit) (pull_request) Successful in 22s
security-scan / Python SCA (pip-audit) (pull_request) Successful in 45s
CI / pre-commit (pull_request) Successful in 1m16s
test-build / build (frontend) (pull_request) Successful in 1m13s
test-build / build (backend) (pull_request) Successful in 1m18s
test-build / build (pull_request) Successful in 0s
CI / frontend-test (pull_request) Successful in 1m29s
CI / backend-test (pull_request) Successful in 3m37s
CI / e2e (pull_request) Successful in 4m30s
to 73d44de58d
All checks were successful
CI / release-scripts (pull_request) Successful in 8s
security-scan / Python SCA (pip-audit) (pull_request) Successful in 56s
security-scan / Python SAST (bandit) (pull_request) Successful in 34s
security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 19s
security-scan / JS SCA (npm audit) (pull_request) Successful in 28s
security-scan / SBOM (trivy) (pull_request) Successful in 16s
CI / pre-commit (pull_request) Successful in 2m24s
CI / frontend-test (pull_request) Successful in 2m20s
test-build / build (frontend) (pull_request) Successful in 1m32s
test-build / build (backend) (pull_request) Successful in 1m39s
test-build / build (pull_request) Successful in 0s
CI / backend-test (pull_request) Successful in 5m9s
CI / e2e (pull_request) Successful in 6m4s
2026-08-13 09:11:46 +00:00
Compare
robbertbos deleted branch tour-waits-for-async-banners 2026-08-13 09:23:23 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
robbertbos/waggle!298
No description provided.