20 Commits
Author SHA1 Message Date
patrickandClaude Sonnet 5 50413c87d8 docs(PROJ-13): clarify OBS-16-7 is not a bug
Platform SYSTEM_ADMIN is deliberately independent of org membership
status (PROJ-13 non-goal: revoke is DB-only, no suspend concept on
users). requireSystemAdmin already reads the flag fresh per request,
so revocation is immediate. QA's finding conflated org-level
suspension with platform-admin revocation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-18 01:12:11 +02:00
patrickandClaude Sonnet 5 60f7671c2e deploy(PROJ-16): Deploy federation status to production
Deployed: 2026-09-18. Server checkout had drifted (36 commits behind,
uncommitted local edits to auth-context.tsx/LoginForm.tsx from prior
direct server edits) — stashed before pull, not discarded. Noted
drizzle-kit --omit=dev migration issue and workaround for future
deploys.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-18 00:40:48 +02:00
patrickandClaude Sonnet 5 2606be571b fix(PROJ-16): correct federation status detection and pagination edge cases
QA round 1 found: HTTP 400 from Synapse was misread as "federation
disabled", falsely confirming isolation on a rejected request instead
of a real disabled state. Only 404 (no such admin endpoint) now maps
to that state; 400 falls through to the 502 error path.

Also fixes: nextToken type mismatch (Synapse sends a string, hook
declared number), empty-state panel wrongly shown on pages beyond the
last result, and a NaN guard when advancing pagination.

AC amended: "letzter erfolgreicher Kontakt" removed — Synapse's
destinations list endpoint has no such timestamp, only retry_last_ts
for failed attempts.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-18 00:23:38 +02:00
patrickandClaude Sonnet 5 9498262836 docs(PROJ-16): add technical design for federation status
Verified the actual admin API against matrix.perlbach24.de before
finalizing the design (lesson from PROJ-14/15, where per-version
response-shape surprises cost a real bug and a wasted defensive code
path): GET /_synapse/admin/v1/federation/destinations returns a clean
{destinations: [], total: 0} instantly with federation disabled — no
surprises, no open questions left for the backend pass. Status set to
In Progress.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0143yYvjesjHndBACoweyK67
2026-09-14 23:52:15 +02:00
patrickandClaude Sonnet 5 509194d3e6 feat(PROJ-15): media cache cleanup for SYSTEM_ADMIN
POST /api/system-admin/media/purge-cache (SYSTEM_ADMIN-only, 10/min
rate limit) plus /system-admin/media page — a date picker (default
30 days back), confirmation dialog, and honest UI copy: this only
clears Synapse's *federated*-media cache, which is nearly empty with
federation disabled. No summary/size-preview endpoint, matching the
tech design's explicit call to not show a number that might be wrong
across Synapse versions.

Backend agent couldn't reach the live Synapse to confirm sync/async
behavior or whether a count comes back (no access to backend/.env in
that sandbox), so it built defensively (number | null). Verified
directly against matrix.perlbach24.de: the call is synchronous (~1.1s)
and does return a real count ({"deleted": 0}). The defensive handling
was unnecessary but harmless (correctly treats 0 as a real count, not
"no data") and stays in as a safety net for other Synapse versions.

Verified end-to-end on talkhub/matrix: purge call returns
{"deleted":0}, audit log gets a proper system_admin.media_cache.purged
entry (organization_id: null), non-admin gets 403, a future cutoff
date is rejected with 400. Test accounts/audit rows cleaned up.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0143yYvjesjHndBACoweyK67
2026-09-13 22:17:00 +02:00
patrickandClaude Sonnet 5 af0562e524 docs(PROJ-15): add technical design for media cleanup
Scoped honestly: Synapse's purge only clears the *remote* (federated)
media cache, which is nearly empty with federation disabled. Local
media now exists in a small way since the Element Web deployment lets
members set profile pictures directly — noted, but out of this
feature's scope (button copy says "Föderations-Medien-Cache" rather
than implying general disk cleanup). No pre-purge size estimate,
since Synapse doesn't reliably support that across versions — flagged
for the backend pass to verify against the actual deployed version
(lesson from PROJ-14's delete_status response-shape surprise). Status
set to In Progress.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0143yYvjesjHndBACoweyK67
2026-09-13 10:32:53 +02:00
patrickandClaude Sonnet 5 beead59907 fix(PROJ-14): address all 6 QA findings on room delete + pagination
BUG-1/BUG-2 (High) — DELETE now polls getRoomDeleteStatus() (up to 10x
1s) before treating the purge as done. "complete" -> drop local mapping
+ audit; "failed" -> 502, mapping left untouched; still "purging" after
the poll window -> 202 {pending:true}, mapping also left untouched.
Frontend handles 202 explicitly instead of assuming success.

Found and fixed a second bug while verifying the first fix live:
getRoomDeleteStatus() assumed a {results:[...]} array response, but
GET .../delete_status/<delete_id> (by delete_id, as used here) returns
a flat object. This silently misread every "complete" as "pending" —
caught only by testing against a real Synapse instance, not by
reading the code.

BUG-3 (Medium) — confirmed live: re-deleting an already-gone room still
returns 200 instead of 404, because Synapse's v2 delete never validates
room existence. Left as-is: the outcome is correct and the operation is
idempotent, so the originally-documented 404 branch is dead code but
not a functional gap.

BUG-4 (Medium) — audit_logs.organization_id is nullable now (migration
0004). Deleting an unassigned room gets a real audit row
(organization_id: null) instead of only a server log warning.

BUG-5 (Medium) — the mapping delete and its audit write now run inside
one db.transaction() (logAudit() takes an optional tx handle); the
delete route has its own 10/min rate limit instead of only the global
100/min.

BUG-6 (Medium) — frontend pagination uses pagination.nextFrom/prevFrom
(Synapse's own cursors) instead of client-side arithmetic against
`total`, which could offer a "Weiter" onto an empty page during a
search.

All verified end-to-end against the live talkhub/matrix Synapse:
fresh orphan room deletes in ~1.1s as "complete" (was a 10s timeout
misreported as pending before the results/flat-object fix); re-deleting
an already-gone room still 200s; a null-org audit row appears after
deleting an unassigned room. Test rooms/accounts/audit rows cleaned up.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0143yYvjesjHndBACoweyK67
2026-09-12 21:46:10 +02:00
patrickandClaude Sonnet 5 4b03ca6c3f feat(PROJ-14): frontend for Synapse room overview
/system-admin/rooms — table (name/alias, members, TalkTime assignment,
created date, delete), search, pagination, delete confirmation dialog
that warns extra hard when the room is still bound to a live team.
SystemAdminRoute gates on user.isSystemAdmin (nav-level only, the
backend routes are the real enforcement).

Also corrected PROJ-13's status to Deployed — it was stuck at
"In Review" despite being live-verified end-to-end already.

Verified: /system-admin/rooms serves the SPA shell (200).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0143yYvjesjHndBACoweyK67
2026-09-12 21:10:44 +02:00
patrickandClaude Sonnet 5 4317df802b docs(PROJ-14): add technical design for Synapse room overview
No new TalkTime DB table — reads Synapse's room list directly and
cross-references the existing chat_rooms table for team/org
attribution. Delete goes straight to Synapse. Status set to In Progress.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0143yYvjesjHndBACoweyK67
2026-09-12 21:02:08 +02:00
patrickandClaude Sonnet 5 0f15432045 feat(PROJ-13): add SYSTEM_ADMIN platform-wide role
users.isSystemAdmin (boolean, default false), DB-only to grant (no UI
in MVP scope, per spec). requesterIsSystemAdmin()/requireSystemAdmin()
in authz.ts — checked fresh from the DB on every request, not cached
in the JWT, so revoking takes effect immediately. GET /api/auth/me
returns the flag; DashboardPage shows a "System-Admin" nav button when
set (convenience only, not the access control — PROJ-14/15/16/18's
own routes enforce it).

/system-admin route doesn't exist yet (comes with PROJ-14) — the nav
button currently bounces back to / via the catch-all route, no stub
page added.

Verified end-to-end on talkhub: /api/auth/me flips true immediately
after a direct DB update, no re-login required.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0143yYvjesjHndBACoweyK67
2026-09-12 20:59:04 +02:00
patrickandClaude Sonnet 5 c1c1ae9c88 docs(PROJ-13): add technical design for SYSTEM_ADMIN role
New boolean flag on the login account (not tied to any organization
membership), checked on every request like the existing org-role
pattern. No UI to grant it — DB-only for MVP. Status set to In Progress.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0143yYvjesjHndBACoweyK67
2026-09-12 20:52:54 +02:00
patrickandClaude Sonnet 5 bbf7a5c80d docs: spec System-Admin-Panel (PROJ-13..PROJ-18), rebuilt from synapse-admin
Six single-responsibility specs covering the synapse-admin feature set
the user wants rebuilt into TalkTime: a new SYSTEM_ADMIN role (platform-
wide, DB-only for MVP — no UI to grant it), room overview, media cache
cleanup (manual trigger only), federation status (mostly a no-op display
since federation is disabled), message reporting in chat, and the admin
moderation view for those reports.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0143yYvjesjHndBACoweyK67
2026-09-12 20:49:36 +02:00
patrickandClaude Sonnet 5 dc1ba9ae27 fix(PROJ-11): BUG-23 (real fix, second attempt) and BUG-25 — QA round 4 gate
BUG-25: DashboardPage header ran ~200px past the viewport at 375px,
"Abmelden" unreachable without horizontal scroll. flex-col/flex-wrap
so it stacks on narrow screens instead of overflowing.

BUG-23: the previous fix (logout_all admin endpoint) never worked —
that route doesn't exist on Synapse (404, swallowed by try/catch).
A second attempt (list + bulk-delete devices) also did nothing,
because loginAsUser()'s admin puppet-login issues a deviceless token
that never appears in the devices list. Live-tested both failures via
account/whoami with the "revoked" token still returning 200.

Actual fix: revokeAccessToken() calls the plain Client-Server /logout
endpoint authenticated as the token being revoked, not as the backend's
admin token — the only mechanism that invalidates a deviceless token.
invalidateMatrixAccessToken() now returns the token it removed so the
route can pass it along. Verified: whoami returns 200 before logout,
401 M_UNKNOWN_TOKEN with the same token right after.

QA round 4 (first with real cross-browser access — Chromium/Firefox/
WebKit at 375/768/1440px) found no remaining Critical or functional
High findings once these two land: production-ready.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0143yYvjesjHndBACoweyK67
2026-09-11 23:38:33 +02:00
patrickandClaude Sonnet 5 8f6e05481b fix(PROJ-11): address QA BUG-2/3/4, partially mitigate BUG-1
BUG-4 (High, failed acceptance criterion) — per-tile unread badges:
new use-team-unread-counts.ts runs one app-level Matrix client (separate
from useTeamChat's detail-view client) that syncs every room GET /api/teams
now resolves (matrixRoomId added via leftJoin on chat_rooms) and reports
unread counts per team. TeamsPage tiles show the badge without the chat
tab being open. Side fix: createMatrixClient() now dynamic-imports
matrix-js-sdk instead of a static import, so the new always-mounted hook
doesn't drag the SDK (and its 7.8MB crypto WASM) back into TeamsPage's
main bundle — verified via build output (TeamDetail chunk 853KB -> 27KB,
SDK now in its own on-demand chunk).

BUG-3 (High) — kick-while-open: use-team-chat.ts now listens for
RoomEvent.MyMembership; leaving/removal flips status to a new "removed"
state instead of leaving history and the composer usable.

BUG-2 (High, partially mitigated) — minted tokens are unscoped Matrix
credentials by nature (Matrix has no token scoping). Disabled
user_directory search on Synapse (config + live matrix.perlbach24.de
instance), verified empty results instead of cross-club user listing.
Direct addressing by known Matrix ID is still possible — full tenant
isolation needs an explicit decision (one Synapse per club vs. an
isolation module on a shared homeserver), documented as an open item
in the spec rather than silently declared fixed.

BUG-1 (Critical, not fixed) — backend/.env.example is outside my
permission sandbox (can't read or write .env* files). Documented the
required block in the spec for manual addition; talkhub's real .env
already has the values.

Verified end-to-end on talkhub/matrix test LXCs. Not addressed in this
pass (lower priority per QA): BUG-5/6/7/8/9/10.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0143yYvjesjHndBACoweyK67
2026-09-11 20:21:07 +02:00
patrickandClaude Sonnet 5 8b6114c3e8 docs(PROJ-11): add technical design for chat feature
Direct browser-to-Synapse connection via matrix-js-sdk, backend only
mints a short-lived per-member access token (no chat proxy, no new
WebSocket server, no new DB table). Status set to In Progress.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0143yYvjesjHndBACoweyK67
2026-09-11 18:52:39 +02:00
patrickandClaude Sonnet 5 4cb2284ad9 docs: spec PROJ-12 — automatic retention-based deletion for left members (GDPR)
Configurable per-organization retention period, daily job, reuses the
existing hard-delete path from PROJ-3 instead of introducing a new one.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0143yYvjesjHndBACoweyK67
2026-09-11 18:47:59 +02:00
patrickandClaude Sonnet 5 568c56a4a7 docs: document password reset and member delete as additions to PROJ-1/PROJ-3
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0143yYvjesjHndBACoweyK67
2026-09-11 18:43:47 +02:00
patrickandClaude Sonnet 5 b1d662fa5b feat: rename CLAUDE.md to TalkTime, retro-document features, add PROJ-11 chat spec
- CLAUDE.md header/description now match actual project (TalkTime), not starter-kit template
- Retro-documented already-built MVP features as PROJ-1..PROJ-10 in features/INDEX.md
- Corrected PROJ-9: Matrix User Provisioning, room sync and reconciliation were already implemented, not just health-check
- Added PROJ-11 requirements spec for basic chat (Matrix Client-Server API, team scope, text-only MVP)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0143yYvjesjHndBACoweyK67
2026-09-11 17:18:22 +02:00
patrickandClaude Sonnet 5 4a19335a1e feat: rework starter kit for self-hosted deploy (Vite/React + Fastify/Drizzle/Postgres)
Replace Next.js/Supabase/Vercel stack with a self-hostable setup: Vite+React
frontend, Fastify+Drizzle+PostgreSQL backend with own JWT auth, deploy via
git pull + rsync + systemd (checkout/service separated, updates pulled from
Gitea). Adds a reference auth route (register/login/me) as the canonical
pattern for new routes. Updates all skills/rules/agents/docs to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BMdBJfD9TT6XUU2NqGG1PN
2026-09-08 11:42:47 +02:00
“alexvisualmakers”andClaude Opus 4.6 600552c858 feat: Migrate from agent markdown files to Skills, Rules, and Sub-Agents
Replace the manual "read .claude/agents/*.md" workflow with native
Claude Code features for a more efficient, scalable development experience:

- **Skills** (.claude/skills/): 7 auto-discovered slash commands
  (/requirements, /architecture, /frontend, /backend, /qa, /deploy, /help)
  with forked sub-agents for heavy tasks and inline execution for interactive ones
- **Rules** (.claude/rules/): 4 modular rule files (general, frontend, backend,
  security) auto-applied based on file context
- **Sub-Agents** (.claude/agents/): Lightweight configs for frontend-dev,
  backend-dev, and qa-engineer with model, tool, and turn limit settings
- **Context Engineering**: Layered context loading, context isolation via
  forked skills, built-in context recovery after compaction, and
  "always read, never guess" rules to prevent hallucinated code references
- **CLAUDE.md**: Auto-loaded project context replacing PROJECT_CONTEXT.md
- **Feature tracking**: features/INDEX.md as persistent state across sessions
- **Production guides**: docs/production/ for error tracking, security,
  performance, database optimization, and rate limiting
- **Init Mode**: /requirements detects empty PRD and bootstraps full project
  setup (PRD + all feature specs) from a single project description

Removed: 6 monolithic agent files, PROJECT_CONTEXT.md, HOW_TO_USE_AGENTS.md,
TEMPLATE_CHANGELOG.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 10:15:27 +01:00