Commit Graph

16 Commits

Author SHA1 Message Date
m
ea65eb7bb7 Merge mai/dokploy/admin-list-actions: per-row actions + English rewrite + segmented tabs + /admin/feedback/new 2026-05-05 18:51:43 +02:00
m
643c356cb6 admin: per-row actions, English rewrite, segmented tabs, /admin/feedback/new
Per-row action bar on the list page:
[Edit] [Copy link] [Open] [Close|Reopen] [Delete] — Delete confirms then
DELETE + invalidateAll(); Close/Reopen PATCHes status, no confirm; per-row
error banner.

Full English rewrite of admin chrome (list + detail + builder), login,
landing. Drop dev jargon — "instance" / "slug" / "schema" / docs/plans
references gone. Sample SAMPLE_FORM content also translated to a
session-feedback example. Participant /f/<slug> stays untouched (author-
supplied content). Results.svelte stays as-is too — shared with the
participant page where the surrounding chrome is German.

Tab strip on /admin/feedback/<id> restyled as a segmented pill bar
(.fb-tabs / .fb-tab / .fb-tab--active). Active tab gets the green
primary-light background + bolder text + radius-md, hover lifts to white.
Earlier tabs were nearly invisible.

Split create form to its own route /admin/feedback/new (page + auth-only
+page.server.ts mirroring the list loader). List page now shows just the
form list with a "+ New form" CTA in the header.
2026-05-05 18:51:38 +02:00
m
f31c1d6f35 Merge mai/dokploy/modern-styling: flexsiebels palette + viewport fix + lifted cards 2026-05-05 18:29:59 +02:00
m
25e3acdfe4 modern styling: flexsiebels palette, viewport reset, lifted cards
- html/body reset (margin 0, bg + color via tokens, fill viewport) — kills
  the white user-agent frame around the dark page in dark mode.
- Replace --fb-* tokens with the flexsiebels variables.css token set
  (--color-*, --radius-*, --shadow-*) and keep --fb-* aliases pointing at
  them so existing class names work without rewriting.
- Page background is now the flexsiebels green gradient (light mode) and
  a charcoal→teal gradient (dark mode).
- Buttons: green primary with shadow + active-press transform, ghost
  variant with proper border + hover.
- Inputs/textareas: rounded-md, focus ring via box-shadow on
  --color-border-focus instead of bare outline.
- Scale buttons: hover hint + green active state with shadow.
- Chat posts + builder cards: white surface with shadow-sm.
- Banners: subtle elevation; dark-mode variants for closed/error so they
  read on the dark gradient.
- Headings: tighter letter-spacing, slightly larger h1.

System dark mode (prefers-color-scheme) still toggles automatically; the
participant page sections stay flat (no re-introduced frame).
2026-05-05 18:29:55 +02:00
m
eadfc39670 Merge mai/dokploy/results-builder-versioning: Ergebnisse tab + live results, form builder, form versioning 2026-05-05 18:24:52 +02:00
m
5e758d49af admin Ergebnisse tab + live results, form builder + JSON view, form versions w/ snapshots
Migration: + fdbck.feedback_instances.live_results_enabled bool default false
           + fdbck.feedback_submissions.form_snapshot jsonb (frozen form per submission)

Schemas (moved $lib/server/schemas.ts → $lib/schemas.ts so the form-validation
Zod runtime can be reused on the client):
- form_definition.version: "0.YYMMDD" (today = 0.260505) with .b/.c suffix
  for same-day re-edits when older snapshots already use that day
- live_results_enabled on Create + Update DTOs

Server:
- submit/+server: writes the parsed form_definition into form_snapshot so
  results stay queryable after the form is later edited
- admin POST: stamps todayVersion() on first save
- admin PATCH: stampVersion() keeps current version while no submission has
  it yet; otherwise advances to today (or .b/.c)
- new $lib/server/results.ts: pure aggregation + version helpers
  (scale → histogram + mean, choices → counts + other_count for vanished
  options, boolean → yes/no, text → list of free-text answers)
- new GET /api/public/feedback/<slug>/results: gated on live_results_enabled,
  strips free-text answers (count-only) for participant-side display
- admin GET + page loader return aggregated results alongside submissions

UI:
- Results.svelte component (shared admin/participant) — CSS bar charts,
  no external lib
- FormBuilder.svelte — add/remove/reorder/edit questions, type switch,
  options/scale config; visual ↔ JSON toggle in admin Edit tab keeps both
  views in sync
- admin detail: new "Ergebnisse" tab with version stamp, "live_results"
  checkbox in Edit tab, info banner about version bumps when submissions exist
- /f/<slug>: after submit (and only if live_results_enabled), polls
  /results every 5s and renders <Results /> below the form
2026-05-05 14:54:03 +02:00
m
cbde51b0f2 Merge mai/dokploy/fix-jsonb-form-definition: parse JSONB form_definition, footer text, flat sections 2026-05-05 12:05:27 +02:00
m
d084fc098b fix /f/[slug]: parse JSONB form_definition, footer text, flatten section frame
- supabase-js with .schema('fdbck') returns JSONB columns as JSON-encoded
  strings; getInstanceBySlug + getInstanceById + admin list now JSON.parse
  via a shared parseFormDefinition helper, so FeedbackFormDefinitionSchema
  sees an object and questions actually render.
- footer: 'flexsiebels.de · per-Link Feedback' → 'fdbck.msbls.de'.
- .fb-section: drop the white card frame (transparent bg, no border, no
  border-radius) — sections now flow flat on the page.
2026-05-05 12:04:29 +02:00
mAi
b914294769 README + design doc copy
- README.md: stack, run-locally, test/check/build, structure tree, data
  model summary, anti-abuse layers, scope notes, issue origin pointer.
- docs/plans/feedback-feature.md: copied verbatim from flexsiebels for
  self-containment (single source of truth in this repo from now on).
2026-05-05 11:38:11 +02:00
mAi
699000c63d Dockerfile: oven/bun:latest, root-run (avoids alpine UID 1000 collision)
Mirrors msbls.de pattern, simplified (no mbrian-core submodule clone).

UID note: oven/bun:1-alpine has a built-in 'bun' user at UID/GID 1000 and
`addgroup -u 1000` on top of it breaks the build silently. mExDraw#14
(commit fc62b9c) lost ~4 weeks of Dokploy deploys to that. Comment in the
Dockerfile so the next person doesn't trip over the same.

Production build verified locally: vite build ✓ (4.08s).
2026-05-05 11:37:36 +02:00
mAi
f9140a414a admin pages (list + detail) + login page (Supabase email/password)
- /admin/feedback (page.server.ts + page.svelte): list with status/mode badges, counts, JSON-editor create form. flex()→fdb() rename done.
- /admin/feedback/[id] (page.server.ts + page.svelte): tabbed detail (Chat / Submissions / Edit), 5s admin polling, hide-toggle, close/reopen, CSV/JSON export, delete. flex()→fdb() rename done.
- /login: simple email + password form posting to /api/auth/sign-in. Pre-redirect if already authed (locals.userId in load). Honours ?redirect= query.

Pages otherwise byte-identical ports of the flexsiebels versions — schema
helper rename happens in /server/fdb.ts.

bun run check: 0 errors, 13 warnings (known false-positive 'data/inst captured
at init'; same pattern flexsiebels has).
2026-05-05 11:36:42 +02:00
mAi
4c68b48417 /f/[slug] participant page (no layout reset hack — whole app is naked)
Direct port from flexsiebels worktree. Imports getInstanceBySlug from
$lib/server/feedback (which uses fdb()) — schema rename happens at the
helper level, page code is identical.

Behaviour:
- LocalStorage: feedback:display_name (global) + feedback:session:<slug>
- 3s polling /posts?since=<latest_ts>; auto-scroll on new
- Hidden posts: '(Beitrag entfernt)' for others; own session sees body + note
- Honeypot 'company' input (CSS-hidden, aria-hidden)
- 423 → closed banner; 429 → rate-limit message; required-validation client+server
- noindex meta + no-referrer
- Question types: short_text, long_text, single_choice, multi_choice, scale, boolean

Root +layout.svelte already gives the naked shell (no sidebar/footer/bottom-nav)
so the +layout@.svelte reset trick is unnecessary here.

bun run check: 0 errors, 5 warnings (known false-positive 'data captured at
init' on $state — data from server load doesn't change client-side; same warning
pattern as flexsiebels).
2026-05-05 11:35:30 +02:00
mAi
946c755f17 feedback API endpoints (port from flexsiebels, fdb() schema rename)
Public (slug-gated, auto-allowlisted):
- GET  /api/public/feedback/[slug]              — instance config
- POST /api/public/feedback/[slug]/submit       — form submission (honeypot, rate-limit, required-validation, 423 if closed)
- GET  /api/public/feedback/[slug]/posts        — chat polling (?since=, hides body of moderated posts)
- POST /api/public/feedback/[slug]/posts        — new chat post (honeypot, rate-limit, 423 if closed)

Admin (requireAuth, owner-scoped):
- GET/POST   /api/admin/feedback                — list/create
- GET/PATCH/DELETE /api/admin/feedback/[id]    — detail/update/delete (PATCH closes/reopens, sets closed_at)
- POST       /api/admin/feedback/[id]/posts/[post_id]/hide — toggle hidden flag
- GET        /api/admin/feedback/[id]/export?format=csv|json — single-file dump

Auth:
- POST /api/auth/sign-in   — Supabase email+password, sets access+refresh cookies
- POST /api/auth/sign-out  — clears cookies

bun run check: 0 errors, 0 warnings.
2026-05-05 11:34:54 +02:00
mAi
f5992ebc5b schemas + rate-limit + feedback helpers + tests
- src/lib/server/schemas.ts: feedback Zod schemas (Question discriminated union + FormDefinition + Instance create/update + Submission/Post/Hide + SignIn).
- src/lib/server/rate-limit.ts (+ test): in-memory token bucket — direct port from flexsiebels.
- src/lib/server/feedback.ts: generateSlug (32-char base62), getInstanceBySlug/ById via fdb(), RATE_LIMIT constants, clampUserAgent.
- src/lib/server/public-scope.test.ts: gate behaviour tests (allowlist coverage + 6 evaluatePolicy cases). Adapted for fdbck's allowlist (no /api/share, no /api/gotify-public).
- @types/bun added so svelte-check resolves bun:test imports — clean baseline (no 'Cannot find bun:test' tech debt that the flexsiebels project carries).

bun run check: 0 errors, 0 warnings.
bun run test: 20/20 pass.
2026-05-05 11:32:23 +02:00
mAi
fa1ad92517 auth + supabase + public-scope hook (mirrors flexsiebels gate, no API keys)
- src/lib/server/supabase.ts: getSupabaseAdmin/Anon (lazy singletons, env-driven URL)
- src/lib/server/fdb.ts: schema accessor for the fdbck Postgres schema
- src/lib/server/auth.ts: cookie-based JWT auth (access+refresh), Supabase getUser/refreshSession. NO API key path — fdbck has no api_keys table; if needed later, add a separate module.
- src/lib/server/request-context.ts + public-scope.ts: public-scope policy gate ported from flexsiebels#59. Allowlist /api/auth/* and /api/public/* by default.
- src/lib/server/response.ts + errors.ts: json/requireAuth + parseBody/handleApiError
- src/hooks.server.ts: validate cookies, set locals.userId, refresh tokens, run handler inside RequestState scope, evaluatePolicy after.
- src/routes/+layout.svelte: minimal naked shell (only loads feedback.css). NO sidebar/footer/bottom-nav per spec.
- src/routes/+page.svelte: brief landing page + admin-login link.
- src/lib/styles/feedback.css: copied verbatim from flexsiebels worktree.

bun run check: 0 errors, 0 warnings.
2026-05-05 11:30:13 +02:00
mAi
ae2984088a skeleton: SvelteKit fullstack app (msbls.de pattern, fdbck variant)
Bootstrap from /home/m/dev/web/msbls.de template:
- SvelteKit 2.15 + Svelte 5 + adapter-node + bun + vite 6
- Deps trimmed: @supabase/supabase-js, postgres, zod (+ dev: kit, vite-plugin-svelte, svelte-check, typescript)
- No mbrian-core submodule (irrelevant for fdbck)
- src/app.html minimal (no fonts, no theme toggler)
- src/app.d.ts declares App.Locals { userId: string | null }
- robots.txt Disallow: / (whole app is naked, per-link or auth-only)
- .env.example: Supabase + PUBLIC_SITE_URL + optional COOKIE_DOMAIN

Initial mai init scaffolding (.claude, .m, .mcp.json, AGENTS.md) bundled in
this first commit since the repo was empty before bootstrap.

Spawned from m/flexsiebels.de#63 pivot — see docs/plans/feedback-feature.md
for the full spec (copied in next commit).
2026-05-05 11:27:59 +02:00