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
8 changes: 7 additions & 1 deletion CONCEPT.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,11 @@ One exclusive `account.role` per account. Roles form a strict hierarchy
`founder > moderator > verified > basis`: a higher role can always do and
see everything a lower role can. New passkey accounts are **Basis**.
`verified` is a moderator confirming this person in real life
(forum badge), not Lightning-Address proof. A **moderator** is proposed by
(forum badge), not Lightning-Address proof. A **funding-program grant** is
independent of that role: moderators review living-room posts against the
three convictions (human decision). `basis` cannot apply. Spend pings and
spend invoices require an admitted grant or a trial on today's UTC day.
A **moderator** is proposed by
an existing moderator and confirmed by a **different** staff
member, or appointed directly by a founder. Those grants persist as trust
edges (`POST /trust/verify`, `POST /trust/propose-moderator`,
Expand Down Expand Up @@ -841,6 +845,7 @@ repository — they're intentionally not part of this project's scope.
| 2026-09-17 | Public Trust Chain picks one incoming kind per subject: the oldest eligible sibling (`createdAt` then `id`). Eligible: `verify`, `moderator_appoint`, and `moderator_propose` only when the subject is a `moderator`. First contact wins; later appoint, confirm, or propose do not replace it. A pending propose (subject still `verified`) stays private. **Supersedes** the 2026-09-17 kind-priority projection (`moderator_propose` if moderator, else `verify`, else `moderator_appoint`). |
| 2026-09-16 | confirm/appoint notify only the subject (`moderator_appointed`); Web Push `url` `/welcome`, tag `moderator_appointed:<subjectId>`; not a living-room fan-out; unique `(recipient, type, reply_id)` with `reply_id` = subject id; missing stores no-op; failure does not fail the trust POST. |
| 2026-09-17 | Staff Bearer `GET /trust/proposals` lists pending `moderator_propose` (verified subject, no confirm/appoint). Session `GET /trust-chain` still omits a pending propose; once the subject is a `moderator`, that propose is eligible as the public incoming edge only when it is the oldest eligible sibling. |
| 2026-09-20 | Funding-program grants (`funding_grant`) are independent of `account.role`. `verified` remains a real-life meeting (forum badge). Moderators review posts against the three convictions. `basis` cannot apply; owner JSON `funding` is `null`. Status none → pending → trial (eligible only on that UTC day) or admitted (recurring) or rejected (may re-apply). Expired trial is effective pending (lazy persist). Spend ping and `POST /invoices` require `eligibleToday`; `GET /invoices/eligible?address=` returns `{ eligible }`. |
| 2026-09-17 | Inbox last-read is per (account, conversation). `GET /conversations` adds per-row `unread` and list `unreadCount`; `POST /conversations/:id/read` stamps last-read. Does not copy DMs into Notifications. |
| 2026-09-17 | Inbound private messages enqueue Web Push (`type: conversation`, url `/messages?c=<id>`, tag `conversation:<id>`) to bell subscribers only. No in-app Notification rows for DMs. Every outbox `unreadCount` (forum, zap, conversation) is notification unread plus listed inbox unread. Push failure does not fail HTTP 200 or Nostr ingest. |
| 2026-09-17 | A living-room note may carry up to 10 JPEG/PNG/WebP stills. Photo 0 stays on `message.photo` (Damus `/photo.jpg` unchanged). Extras 1–9 live in `message_extra_photo` and are served at `/messages/:id/photo/1.jpg` … `/photo/9.webp`. Public JSON includes `photoCount` (0–10). POST accepts `photos[]` (max 10) and still accepts singular `photo`. Video stays exclusive (poster = photo 0, no extras). |
Expand All @@ -854,6 +859,7 @@ repository — they're intentionally not part of this project's scope.
| 2026-09-20 | Passkey authenticate/register finish and operator debug session mint refuse an account whose stored `sessionRefused` flag is true: HTTP 403 with the wrong-account error and no bearer. `GET /me` with an already-minted token for that row is the same 403 so the client can sign the visitor out. Other authenticated routes treat that token as missing (401). Operators set the flag with `PATCH /debug/accounts/:id`. The account row is not deleted. **Supersedes** the same-day listed-id copy of this row. |
| 2026-09-20 | A paid moderator stipend in the closed Moderators group carries a durable `gift_for_message_id` / public `giftFor` link to the group message that triggered it, so the app can render the stipend row attached under that message. Absent or null on every other conversation row. |
| 2026-09-21 | `GET /conversations` (and other public conversation list rows) include per-row `unreadMessageCount` (inbound messages strictly after last-read; `0` when none). `unread` stays `unreadMessageCount > 0`. Envelope `unreadCount` remains the number of listed unread threads (menu/PWA badge). Gift-only inbound counts; outbound does not. |
| 2026-09-21 | Optional `GET /messages?hashtag=` token filter on live top-level `text` (name without `#`; token match; combines with `mode`/`limit`/`cursor`). No new entity, table, or index. A shops page of 20 is 20 matching notes, not 20 mixed notes filtered later. |

## Next Steps

Expand Down
Loading
Loading