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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/content/docs/changelog/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Notable changes to the kit, newest first.

## 2026-11-03

- **Dashboard: no more `403` polls for users without the permission (fix).** The topbar notification bell (`GET /notifications/unread-count`), the topbar chat unread pill (`GET /chat/channels`) and the overview usage card (`GET /billing/usage`) queried their endpoints for every user, so a role without `Permissions.Notifications.Inbox.View`, `Permissions.Chat.Channels.View` or `Permissions.Billing.View` got a `403` on each poll and the API logged an error line for each one. Each query now runs only when the user holds that permission. Without it, the bell and the chat pill are hidden, and the usage card and the Resources stat card say there is no access. The `/chat` and `/subscription` pages are unchanged, because the nav already hides them from users without the permission. See [Add a dashboard page](/docs/guides/add-a-dashboard-page/#step-5---permission-gating-nav--actions-not-routes) and [#1445](https://github.com/fullstackhero/dotnet-starter-kit/pull/1445).
- **Frontend containers: the inactivity timeout is configurable from the environment (fix).** Both apps read `inactivityIdleMs` and `inactivityWarningMs` from `/config.json`, but the container's `config.json.template` never carried them, so an operator could not change the auto sign-out time without rebuilding the image. The template now renders them from `FSH_INACTIVITY_IDLE_MS` and `FSH_INACTIVITY_WARNING_MS`. Unset or empty keeps the built-in timings (admin 10 minutes, dashboard 20, each with a 60-second warning), so existing deployments behave as before; a value that is not a positive integer stops the container at start with a clear error rather than rendering invalid JSON. `deploy/docker` exposes them as `FSH_ADMIN_INACTIVITY_IDLE_MS`, `FSH_DASHBOARD_INACTIVITY_IDLE_MS` and `FSH_INACTIVITY_WARNING_MS` in `.env`. The Terraform static-site `runtime_config` and the Aspire dev setup are unchanged. See [Frontend development](/docs/frontend/development/#the-container-pattern) and [#1442](https://github.com/fullstackhero/dotnet-starter-kit/pull/1442).
- **Dashboard: the overview welcomes the tenant by its name (fix).** The first-run card on a tenant with no subscription read "Welcome to" followed by the tenant identifier (for example `qpb2edshbj`), and the header caption showed the same identifier, because both came from the token's `tenant` claim. Both now use the tenant name from `GET /api/v1/tenants/me/status`, which the overview already loads for its "Valid for" card, and fall back to the identifier while that call is loading or has failed. No backend change. See [Tenant dashboard](/docs/frontend/dashboard/#page-inventory) and [#1437](https://github.com/fullstackhero/dotnet-starter-kit/pull/1437).
- **Both apps: non-ASCII claims in the access token now render correctly (fix).** `decodeJwt` decoded the token payload with a bare `atob`, which returns one character per byte, while the payload is UTF-8. A display name like "Comitê de Ética" therefore showed as "Comitê de Ética" in the user menu and the overview greeting, even though the token and the database were correct. The payload bytes are now decoded as UTF-8 in both the dashboard and the admin console. No configuration change. See PR [#1439](https://github.com/fullstackhero/dotnet-starter-kit/pull/1439).
Expand Down
6 changes: 4 additions & 2 deletions src/content/docs/guides/add-a-dashboard-page.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Add a page to the tenant dashboard
lastUpdated: 2026-06-11
lastUpdated: 2026-10-14
description: End-to-end recipe for adding a new page to the tenant dashboard app - API module, page component, lazy route, sidebar nav entry, permission gating, and a route-mocked Playwright test.
sidebar:
label: Add a dashboard page
Expand Down Expand Up @@ -188,6 +188,8 @@ How permissions actually flow in the dashboard (verified in `src/auth/auth-conte

Gate the nav entry (step 4) and any in-page actions (hide a Restore button, hide a tab) with `user.permissions.includes(...)`. The server keeps enforcing everything as defence-in-depth - client gating is UX, not security.

A query that mounts outside its own nav-gated page (a topbar badge, an overview card) needs the same gate on the query itself, or every user without the permission polls a `403` that the API logs as an error. Copy the topbar notification bell: `enabled: permissionsHydrated && (user?.permissions.includes(INBOX_VIEW_PERMISSION) ?? false)`, then hide the piece or render a no-access state when the check fails. A manual `refetch()` ignores `enabled`, so wrap it in the same check.

## Step 6 - Playwright test (`tests/projects/projects.spec.ts`)

Tests are **route-mocked and JWT-seeded** - no real backend. `playwright.config.ts` auto-boots `npm run dev`. The harness (in `tests/helpers/`):
Expand Down Expand Up @@ -224,7 +226,7 @@ Mock everything the page touches when it mounts - not just the endpoint you're a

Two more harness gotchas, both verified in the existing suite:

- **Permission-gated UI**: the shell mock returns `[]` for `GET /identity/permissions`, so gated entries are hidden by default. Re-mock it with the permissions your test needs (see `tests/system/trash.spec.ts`).
- **Permission-gated UI**: the shell mock answers `GET /identity/permissions` with `SHELL_PERMISSIONS`, the Basic-role grants the shell's own queries need (notification inbox, chat channels, billing view), so every other gated entry is hidden by default. Re-mock it with the permissions your test needs (see `tests/system/trash.spec.ts`), or with `[]` to test the no-access path (see `tests/layout/permission-gated-queries.spec.ts`).
- **Strict-mode double match**: list pages render a mobile card list and a desktop table of the same data. Scope assertions to one of them (the tickets spec filters to the desktop card) or `getByText` will find two elements.

## Step 7 - Verify
Expand Down