A home page at /, and members create their own boards #194

Closed
opened 2026-09-18 17:12:51 +00:00 by Grandiras · 5 comments
Owner

Part of #190; needs #192 and #193. This is the step a member sees: / stops being the default board and becomes the place you start from, and making a board stops being something you ask an admin for.

Design it in Lunacy first (design/Ponente.free): desktop, phone, and the empty state, in both themes. The page is a launchpad, not a dashboard — docs/brand.md on dense copy applies to it more than to anything else in the app.

The page

  • A tile per board you can see, wearing its BoardPalette colour. Counts on the tile (open cards, yours, due soon) only if they can be had without decrypting every card on every board for every visit — check docs/data.md before promising them; a tile with a name and a colour is a fine first version.
  • Create a board, as a tile, and as the whole page when there are none. The creator is its admin and the board is private to them (#190). It is still seeded with To Do / Doing / Done.
  • The wordmark goes to /. This reverses the HomeHref decision in docs/boards-and-permissions.md, whose reason was that a bare path meant the default board and so quietly moved people onto somebody else's. / is no longer a board, so the reason is gone; the board title beside it is still the switcher.

What goes away

The default board ("first in switcher order"), LastBoardException and the board.none empty state: having no boards is now an ordinary state with a button on it. The bare board-scoped paths lose their meaning with it — /calendar becomes the general calendar (/calendar/all exists already), /board goes home.

/ resumes, but only from outside

The table in #190 is the spec. A document request for / carrying Sec-Fetch-Site: none — typed, bookmarked, launched — with a remembered page other than / is redirected there, through the same validated path /launch uses (#191). Anything else renders the home page: a click from inside the app, a reload of it, a browser that does not send the header. Visiting the home page records / like any other place, which is what makes reloading it stay put. /launch remains, as an alias, for installs that already have it. The remarks on LastPage that explain why / must never restore are the ones to rewrite.

Migrating

A bookmark of / used to mean a board and now means "where I was", which for anybody with one board is the same place. /board/{id} links are untouched. Nothing in the database moves in this step.

Tests and docs

A new home.spec.js: tiles are the boards BoardAccess allows and no others; creating a board lands on it as its admin and nobody else sees it; the four rows of the resume table (a plain goto is a navigation from outside and a click on the wordmark is one from inside, so both halves are reachable without faking a header). boards.spec.js and everything that assumes / is a board will need a look — devLogin lands there. Docs: boards-and-permissions.md, ui.md, orientation.md's routes, and a changelog line in both locales.

Part of #190; needs #192 and #193. This is the step a member sees: `/` stops being the default board and becomes the place you start from, and making a board stops being something you ask an admin for. **Design it in Lunacy first** (`design/Ponente.free`): desktop, phone, and the empty state, in both themes. The page is a launchpad, not a dashboard — `docs/brand.md` on dense copy applies to it more than to anything else in the app. ## The page - **A tile per board you can see**, wearing its `BoardPalette` colour. Counts on the tile (open cards, yours, due soon) only if they can be had without decrypting every card on every board for every visit — check `docs/data.md` before promising them; a tile with a name and a colour is a fine first version. - **Create a board**, as a tile, and as the whole page when there are none. The creator is its admin and the board is private to them (#190). It is still seeded with To Do / Doing / Done. - **The wordmark goes to `/`.** This reverses the `HomeHref` decision in `docs/boards-and-permissions.md`, whose reason was that a bare path meant *the default board* and so quietly moved people onto somebody else's. `/` is no longer a board, so the reason is gone; the board title beside it is still the switcher. ## What goes away The default board ("first in switcher order"), `LastBoardException` and the `board.none` empty state: having no boards is now an ordinary state with a button on it. The bare board-scoped paths lose their meaning with it — `/calendar` becomes the general calendar (`/calendar/all` exists already), `/board` goes home. ## `/` resumes, but only from outside The table in #190 is the spec. A document request for `/` carrying `Sec-Fetch-Site: none` — typed, bookmarked, launched — with a remembered page other than `/` is redirected there, through the same validated path `/launch` uses (#191). Anything else renders the home page: a click from inside the app, a reload of it, a browser that does not send the header. Visiting the home page records `/` like any other place, which is what makes reloading it stay put. `/launch` remains, as an alias, for installs that already have it. The remarks on `LastPage` that explain why `/` must never restore are the ones to rewrite. ## Migrating A bookmark of `/` used to mean a board and now means "where I was", which for anybody with one board is the same place. `/board/{id}` links are untouched. Nothing in the database moves in this step. ## Tests and docs A new `home.spec.js`: tiles are the boards `BoardAccess` allows and no others; creating a board lands on it as its admin and nobody else sees it; the four rows of the resume table (a plain `goto` is a navigation from outside and a click on the wordmark is one from inside, so both halves are reachable without faking a header). `boards.spec.js` and everything that assumes `/` is a board will need a look — `devLogin` lands there. Docs: `boards-and-permissions.md`, `ui.md`, `orientation.md`'s routes, and a changelog line in both locales.
Collaborator

🤖 On it — the reply will link the full session transcript.

🤖 On it — the reply will link the full session transcript. <!-- opencode-run:task -->
Collaborator

Now write home.spec.js:

The run reported an error after replying: This operation was aborted

→ Opened PR: #203


⏱ 45m 4s · 298 steps · $2.1451 · tokens: 2348.7k in / 93.7k out (+58200.3k cache-read) · Go: 5h 88% · wk 60% · mo 32% ⚠️

📜 Full session transcript

Now write home.spec.js: _The run reported an error after replying: This operation was aborted_ → Opened PR: https://git.grandiras.net/Grandiras/Ponente/pulls/203 --- ⏱ 45m 4s · 298 steps · $2.1451 · tokens: 2348.7k in / 93.7k out (+58200.3k cache-read) · Go: 5h 88% · wk 60% · mo 32% ⚠️ 📜 [Full session transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/Ponente/issue-194/run-2026-09-18T20-31-28-358Z.md) <!-- opencode-session:ses_f49c87395ffeS55e5DOVsfsePw -->
Author
Owner

@claude-bot Please redo this.

@claude-bot Please redo this.
Collaborator

🤖 On it — follow along in the action run; the reply will link the full session transcript.

📜 Live transcript — updating every ~5 minutes while the task runs.

🤖 On it — follow along in [the action run](https://git.grandiras.net/Grandiras/claude-bot/actions/runs/561); the reply will link the full session transcript. <!-- claude-run:comment --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/issue-194/run-561.md) — updating every ~5 minutes while the task runs.
Collaborator

Redone from scratch on main — branch claude/issue-194 (PR #203 was closed, nothing reused from it).

What / is now

The home page: a tile per board, with the shape of its columns, a count, who else can see it, and the settings cog on a board you run

A tile per board you can see, wearing the colour BoardPalette already gives it on the general calendar, plus the way to one more. Everything a tile says is structure, which is what makes the page affordable on the address every visit starts at: BoardService.GetTilesAsync is three queries for the whole list (the boards, a count per column, the grant rows) and decrypts nothing but each board's own name. That is also the line it stops at — "due soon" cannot be had, because Card.DueDate is ciphertext and the only route to it is decrypting every card on every board, every visit. The little picture is a track per column and a dash per card; who else sees a board is said in words and faces (Everyone / Only you / a face stack), never with a lock.

The six Home frames from #199 were the spec — I read the copy and the geometry out of design/Ponente.free directly and copied the strings back into LocaleService (home.*), which closes the exception docs/frontend.md recorded. I could not open Lunacy from CI, so the canvas itself is untouched; the pass it now needs is filed below.

Making a board is a tile in the same grid, and having none is an ordinary state with a button on it

A board a member makes is Restricted to them, with their own admin grant — starting something of your own is not an act of publishing — and still arrives with To Do / Doing / Done. The seeded board stays everyone's. /admin/boards is gone (it redirects home), and the Admin flyout it lived in opened onto a single row after that, so it is a plain Members link now, on the desktop row and in the phone sheet.

On a phone the tiles are rows and the bar's first cell is Home

On a phone the bar's first cell is Home when you are off a board and Board when you are on one; the way back from a board is a row in the account sheet, since the wordmark is not in that bar. The canvas could not show that case.

/ resumes, but only from outside

LastPage.ResumeFromOutside + a middleware in Program.cs (an endpoint can't do it — a literal MapGet("/") outranks the Blazor router's catch-all). All four rows of #190's table behave, checked in a real browser.

The service worker silently broke it, and that is the thing worth knowing. service-worker.js intercepted navigations and re-issued them with fetch(event.request) — a request made by the worker, so Chromium computes its own Fetch Metadata for it and the server saw Sec-Fetch-Site: same-origin where the member's navigation said none. With a worker installed, / resumed for nobody who had been here before, which is precisely the audience. Fixed with navigation preload (navigationPreload.enable() + await event.preloadResponse); Safari has none, and there /launch — still the manifest's start_url — is unaffected because a redirect is not a rule about headers.

Also gone

The default board (GetDefaultAsync, BoardOutcome.None, board.none), LastBoardException, HomeHref, and the bare board-scoped paths: /calendar is the general calendar, and an .ics link with no board now means every board rather than 404ing a subscription that has been syncing for a year.

Verified

  • dotnet build and dotnet test Ponente.sln — 493 pass, DocumentationTests included.
  • e2e: no container engine in this job, so I ran the app against local Postgres and pointed Playwright at it — 124 of 132 pass over the 16 runnable spec files. The 8 failures are all environment, and I checked each: 3 need the PDS container (home.spec.js's second member, both requests.spec.js flows), 2 hard-code localhost:8090 (i18n, share), 1 needs the stub forge, 1 is the phone timeline ordering (passes on a clean board; this run leaves ~60 cards behind because the psql teardown can't reach a container), and 1 is the search box's centring — which I reproduced on main at 31px before touching anything, so it is filed rather than fixed.
  • New: home.spec.js; helpers/boards.js now holds go-home/open-board/create/delete, which eight spec files had each copied. prefs.spec.js, board-entrance.spec.js and share.spec.js needed real fixes beyond the mechanical ones (an old /?card= link shape, the entrance measuring a board it no longer landed on). One thing fixed in passing: mobile.spec.js referenced an undefined tagsLink, which is a ReferenceError on main.
  • Docs updated in the same commit: boards-and-permissions.md (a new home page section and the reversed decisions, with their reasons), ui.md (the resume rule and the service-worker trap), orientation.md, data.md, calendar.md, frontend.md, testing.md, CLAUDE.md. Changelog in both locales — condensed to stay inside the seven-line cap, which meant merging two earlier bullets.

⏱ 75m 19s · 361 turns · tokens: 593.4k in / 227.6k out (+129476.4k cache-read)
📜 Full session transcript
→ Opened PR: #204
→ Filed follow-up issue #205: The header's search box drifts off centre once the account side has four links
→ Filed follow-up issue #206: Bring the Lunacy canvas back in step with the home page and the admin nav

Redone from scratch on `main` — branch **`claude/issue-194`** (PR #203 was closed, nothing reused from it). ## What `/` is now ![The home page: a tile per board, with the shape of its columns, a count, who else can see it, and the settings cog on a board you run](https://git.grandiras.net/attachments/6f1cdeb0-de9b-4ea7-988f-f2d463ad0744) A tile per board you can see, wearing the colour `BoardPalette` already gives it on the general calendar, plus the way to one more. Everything a tile says is **structure**, which is what makes the page affordable on the address every visit starts at: `BoardService.GetTilesAsync` is three queries for the whole list (the boards, a count per column, the grant rows) and decrypts nothing but each board's own name. That is also the line it stops at — "due soon" cannot be had, because `Card.DueDate` is ciphertext and the only route to it is decrypting every card on every board, every visit. The little picture is a track per column and a dash per card; who else sees a board is said in words and faces (*Everyone* / *Only you* / a face stack), never with a lock. The six Home frames from #199 were the spec — I read the copy and the geometry out of `design/Ponente.free` directly and copied the strings back into `LocaleService` (`home.*`), which closes the exception `docs/frontend.md` recorded. I could not open Lunacy from CI, so the canvas itself is untouched; the pass it now needs is filed below. ![Making a board is a tile in the same grid, and having none is an ordinary state with a button on it](https://git.grandiras.net/attachments/0edcc5f8-8671-4178-87a9-22fa015c4b0b) A board a member makes is **`Restricted` to them, with their own admin grant** — starting something of your own is not an act of publishing — and still arrives with To Do / Doing / Done. The seeded board stays everyone's. `/admin/boards` is gone (it redirects home), and the Admin flyout it lived in opened onto a single row after that, so it is a plain **Members** link now, on the desktop row and in the phone sheet. ![On a phone the tiles are rows and the bar's first cell is Home](https://git.grandiras.net/attachments/90456fe2-f893-480a-af1c-360f0a7e2aab) On a phone the bar's first cell is Home when you are off a board and Board when you are on one; the way back from a board is a row in the account sheet, since the wordmark is not in that bar. The canvas could not show that case. ## `/` resumes, but only from outside `LastPage.ResumeFromOutside` + a middleware in `Program.cs` (an endpoint can't do it — a literal `MapGet("/")` outranks the Blazor router's catch-all). All four rows of #190's table behave, checked in a real browser. **The service worker silently broke it, and that is the thing worth knowing.** `service-worker.js` intercepted navigations and re-issued them with `fetch(event.request)` — a request made by the *worker*, so Chromium computes its own Fetch Metadata for it and the server saw `Sec-Fetch-Site: same-origin` where the member's navigation said `none`. With a worker installed, `/` resumed for nobody who had been here before, which is precisely the audience. Fixed with navigation preload (`navigationPreload.enable()` + `await event.preloadResponse`); Safari has none, and there `/launch` — still the manifest's `start_url` — is unaffected because a redirect is not a rule about headers. ## Also gone The default board (`GetDefaultAsync`, `BoardOutcome.None`, `board.none`), `LastBoardException`, `HomeHref`, and the bare board-scoped paths: `/calendar` is the general calendar, and an `.ics` link with no `board` now means every board rather than 404ing a subscription that has been syncing for a year. ## Verified - `dotnet build` and `dotnet test Ponente.sln` — **493 pass**, `DocumentationTests` included. - e2e: no container engine in this job, so I ran the app against local Postgres and pointed Playwright at it — **124 of 132 pass** over the 16 runnable spec files. The 8 failures are all environment, and I checked each: 3 need the PDS container (`home.spec.js`'s second member, both `requests.spec.js` flows), 2 hard-code `localhost:8090` (`i18n`, `share`), 1 needs the stub forge, 1 is the phone timeline ordering (passes on a clean board; this run leaves ~60 cards behind because the psql teardown can't reach a container), and 1 is the search box's centring — which I reproduced on `main` at 31px before touching anything, so it is filed rather than fixed. - New: `home.spec.js`; `helpers/boards.js` now holds go-home/open-board/create/delete, which eight spec files had each copied. `prefs.spec.js`, `board-entrance.spec.js` and `share.spec.js` needed real fixes beyond the mechanical ones (an old `/?card=` link shape, the entrance measuring a board it no longer landed on). One thing fixed in passing: `mobile.spec.js` referenced an undefined `tagsLink`, which is a `ReferenceError` on `main`. - Docs updated in the same commit: `boards-and-permissions.md` (a new *home page* section and the reversed decisions, with their reasons), `ui.md` (the resume rule and the service-worker trap), `orientation.md`, `data.md`, `calendar.md`, `frontend.md`, `testing.md`, `CLAUDE.md`. Changelog in both locales — condensed to stay inside the seven-line cap, which meant merging two earlier bullets. --- ⏱ 75m 19s · 361 turns · tokens: 593.4k in / 227.6k out (+129476.4k cache-read) 📜 [Full session transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/issue-194/run-561.md) → Opened PR: https://git.grandiras.net/Grandiras/Ponente/pulls/204 → Filed follow-up issue [#205](https://git.grandiras.net/Grandiras/Ponente/issues/205): The header's search box drifts off centre once the account side has four links → Filed follow-up issue [#206](https://git.grandiras.net/Grandiras/Ponente/issues/206): Bring the Lunacy canvas back in step with the home page and the admin nav
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
3 participants
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
Grandiras/Ponente#194
No description provided.