Commit Graph

34 Commits

Author SHA1 Message Date
mAi
439b030471 mAi: #1 - server: date_ranked_choice aggregation + export + tests
- `aggregateResults` ingests rating maps, tallies per-option counts +
  histogram (1-5 buckets) + running sum, then `finalise` computes per-option
  means and sorts options by mean desc with tiebreaks (count of 5s, then
  4s, then total count, then id). Question-level `count` reflects
  submissions that rated at least one option.
- Out-of-range, fractional, and non-integer ratings are silently dropped —
  the aggregator never trusts user data, schema validates it on submit.
- CSV export expands a date_ranked_choice question into one column per
  option named `<qid>[<optid>]`. JSON export is unchanged (it serialises
  the rating map directly).
- New `results.test.ts` covers: per-option counts and means, histogram
  tallying, mean-with-tiebreak ordering, ignoring bad ratings, and missing
  answers. Wires the file into the `bun test` script.

Refs m/fdbck#1.
2026-05-06 14:13:11 +02:00
mAi
91098e0965 mAi: #1 - schema: date_ranked_choice question type
New discriminated-union variant for `FeedbackQuestionSchema`:

  { type: 'date_ranked_choice',
    options: [{ id, start, end?, label? }, …],
    scale?: { min_label?, max_label? },
    allow_partial?: boolean }

- Times stored as UTC ISO 8601 strings (datetime with offset). Author UI
  feeds them through datetime-local inputs that the browser already treats
  as local time; renderer converts back to viewer-local on display.
- Rating scale is locked at 1-5 (5-point Likert) per design — the `scale`
  field exposes only labels, not min/max bounds.
- Per-option ids are 1-64 chars, alphanumeric + `-`/`_`, must be unique.
- 2-50 options per question.

Submission answer union extended with a `Record<string, 1|2|3|4|5|null>`
shape for the per-option rating map (`{ opt1: 5, opt2: null }`).

Refs m/fdbck#1.
2026-05-06 14:13:01 +02:00
mAi
4259f16d45 Merge mai/cronus/builder-on-new: visual ↔ JSON editor toggle on /admin/feedback/new 2026-05-06 14:06:06 +02:00
mAi
3cc1efa970 ui: visual ↔ JSON toggle on /admin/feedback/new (t-fdbck-builder-on-new)
m's complaint: "I already want the visual editor/json editor switch — why
only after creating an empty form, that makes no sense". Three steps to
get to the obvious starting place — create empty, navigate to detail,
switch tab — is friction.

Mirrors the detail-page Edit-tab pattern verbatim:

- editMode / editForm / editFormJson state, plus syncJsonFromVisual /
  syncVisualFromJson / switchEditMode helpers ported 1:1 from
  /admin/feedback/[id]/+page.svelte. The two pages now author questions
  the same way.
- Default mode: Visual, with a null editForm + "No questions yet." +
  "+ Add questions" button. Clicking the button calls ensureBuilderForm()
  which seeds the same { id: 'q1', label: 'Question 1', type: 'short_text',
  required: false } stub the detail page seeds.
- JSON mode unchanged: textarea + "Insert sample" + helper text.
- Submit logic resolves form_definition from whichever mode is active
  (mirrors detail-page saveEdits parsedForm branch).
- Disclosure framing kept ("Add questions now (advanced)") — collapsed by
  default so the title-+-chat-only path stays uncluttered.

Reuses FormBuilder.svelte directly. No new component, no new dep.
POST /api/admin/feedback contract unchanged.
2026-05-06 14:05:55 +02:00
mAi
006ecf442a Merge mai/cronus/fdbck-minimalist-ui: minimalist redesign — cards, header collapse, ⋯ menus, spacing scale 2026-05-06 12:38:28 +02:00
mAi
4ab4dc5c2d ui: tokenise .fb-question spacing + dark-mode QA notes
Final polish + verification commit. Tokenises the last hard-coded
margin in .fb-question (1.25rem → var(--space-4)) so the spacing scale
introduced in commit 1 is the single source of truth. Visually
identical (1.25rem === --space-4); the payoff is that any future
adjustment to the field-gap token propagates here automatically.

Optimistic status toggle was implemented inline in the list (commit 4)
and detail (commit 5) pages with a revert-on-failure path — no further
sweep needed.

Dark-mode QA — verified at 375 / 1024 / 1440 widths via headless
Chromium with --enable-features=WebContentsForceDark + --force-dark-mode
(token cascade confirmed firing: the rendered gradient matches the dark
--gradient-bg in feedback.css, not Chrome's auto-dark inversion of the
light gradient):

- /  — wordmark + tagline + ghost CTA centred, dark-teal gradient ✓
- /login — vertical-centred form, white-on-dark fields, primary CTA ✓

Admin pages and /f/[slug] need post-deploy verification once head
merges + deploys this branch — they require auth + a real form slug
which the local preview can't supply. The token cascade is shared
with these pages so visual regressions are unlikely; functional QA
of the optimistic toggle, ⋯ menu click-outside, and share-strip
short-link flow should happen against fdbck.msbls.de.
2026-05-06 12:37:26 +02:00
mAi
d9e4bfc165 ui: minimalist /f/[slug] polish
- Drop the inline-label name row (.fb-name-row) — the only
  inline-label-with-input pattern in the app. Replace with the same
  stacked-label .fb-question pattern every other field uses.
- Closed-state: switch from amber .fb-banner--closed to a quiet neutral
  .fb-closed-line (italic muted text between two thin border lines).
  Closed is a state, not an alert.
- Mine-post chat bubble: drop the full primary-coloured border in favour
  of a 3px left-border-only accent. Less loud, still recognisable.
- Footer "fdbck.msbls.de" wraps as a permalink to / with hover affordance.

German strings on this page are unchanged per m's override (m/fdbck#3
will handle proper i18n separately).
2026-05-06 12:31:55 +02:00
mAi
94f6ba934d ui: minimalist /admin/feedback/[id] detail — header collapse + share strip
The header was the densest surface in the app: 8 controls in a single
flex-wrap row plus a separate Share section bolted between header and tabs.
This commit collapses both into something readable.

Header:

- 8 visible controls → 3 visible. Status pill is now clickable and toggles
  between open/closed (optimistic), replacing the Close/Reopen button. The
  ⋯ menu absorbs Copy /f/<slug>, Export CSV, Export JSON, and a separator
  before Delete (still in red). All gone from the top-row strip: the raw
  /f/slug, Copy link, Preview, Close/Reopen, CSV, JSON, Delete buttons.
- Quiet text-only .fb-back-link replaces the chip-style "← All forms"
  button.
- New .fb-detail-head primitive lays out title-block on the left + actions
  on the right with proper flex-wrap behaviour.

Share:

- Standalone <section data-fb-share> deleted. Its job moves to a new
  inline .fb-share-strip directly under the title in the header.
- Strip always shows a usable URL: short_url if it exists, else the raw
  /f/<slug>. Copy + Open ↗ buttons sit alongside.
- Below the strip, a compact <details class="fb-share-strip__replace">
  holds the slug input + Create/Replace button. Summary text adapts to
  whether a short link already exists.

Tab body:

- Drop the inner <h2> in every tab body (the active tab pill names the
  section). All four tab bodies now use .fb-tab-body for consistent top
  padding (var(--space-6)).
- "X responses already received…" warning becomes a muted .fb-question__help
  line, not a .fb-banner box.
- Visual / JSON toggle becomes a real .fb-segment control matching the
  shape of .fb-tabs (consistency).
- Save row uses .fb-save-row with the version pill ("Current version: vN")
  rendered as a quiet .fb-version-note next to the Save button instead of
  decorating the H2 like before.
- Submissions table extracted to a small <style> block (.fb-detail-table)
  instead of inline style="..." chunks.

Click-outside-to-close + Escape close any open ⋯ menu, mirroring the list
page. Polling, refresh, and all backend contracts unchanged.

Delete still uses confirm() per m's override — deletion remains a deliberate
two-step action, no undo toast.
2026-05-06 12:31:00 +02:00
mAi
80f2f82ac1 ui: minimalist /admin/feedback list — cards + ⋯ menu + status pill toggle
m chose cards over a spacious-list pattern. They're MINIMALIST cards: subtle
.fb-card bg on the gradient page bg, no border + shadow stack, generous
internal padding, plenty of negative space between cards (1.5rem mobile,
2rem desktop). 2-up at ≥640px so they breathe without widening the shell.

Per-row simplifications:

- Drop the H2 "Your forms (N)" — the cards are the count.
- Drop the descriptive paragraph in the header — single primary CTA on the
  right is the entire header.
- Card title is the link to detail. The "Edit" button becomes implicit.
- Subtitle merges mode + counts on one muted line; the "created DATE" line
  and the raw "/f/<slug>" line both go away (slug is in the menu, date is
  available on the detail page).
- Right side: a clickable .fb-status-pill that flips status optimistically,
  next to a .fb-menu (⋯ trigger + native <details> panel) holding Copy link
  / Open / Edit / ────── / Delete.
- Optimistic status toggle: pill flips instantly, PATCH fires in background,
  reverts to server state on failure. Status is reversible so this is safe.
- Delete still uses the existing confirm() modal (m's override — no undo
  toast, deletion remains a deliberate two-step action).
- All inline style="..." removed except a tiny hoisted .fb-list-head style
  block for the header layout.

Click-outside-and-Escape close any open ⋯ menu — added via document-level
listener in onMount, cleaned up in onDestroy.

Empty state gets generous whitespace + a primary "Create your first form"
CTA in the .fb-empty container.
2026-05-06 12:28:21 +02:00
mAi
5080f39079 ui: minimalist /admin/feedback/new
- Drop the page subtitle ("Set up a feedback form, a live chat session…").
  The H1 + the form below carry meaning on their own.
- Replace the chip-style back-button with a quiet text-only .fb-back-link.
- Replace the inline-checkbox-as-fb-option-row chat toggle with a proper
  .fb-toggle (label-left + hint + native checkbox-right).
- Tuck the JSON-questions textarea + sample button + helper text behind a
  <details> disclosure labelled "Add questions now (advanced)".
  The visual builder on the detail page is the canonical path; the JSON
  paste at creation time is a power-user speed-up that no longer dominates
  the page. Common path now reads as 4 inputs and a button.
- Move the "Insert sample" button inside the disclosure where it belongs.

Backend untouched. /api/admin/feedback POST contract unchanged.
2026-05-06 12:27:11 +02:00
mAi
b1ee5530fd ui: minimalist landing + login
- /: vertical-centred narrow shell, wordmark grows to 2.5rem with -0.03em
  tracking, tagline simplified to "feedback by link", single ghost CTA
  to /login. Drops the redundant "this page is only reachable through a
  private link" sentence (the user is already here).
- /login: vertical-centred narrow shell, drops "Admin access only."
  subtitle (URL says it), error moves from .fb-banner--error block above
  the button to .fb-inline-error muted text below it (no layout shift,
  less alarm).
2026-05-06 12:26:15 +02:00
mAi
2593122337 style: CSS foundation for minimalist UI redesign
Adds the spacing scale, status-pill tokens, and a set of new utility classes
that the per-screen commits will use:

- spacing scale: --space-1 through --space-9 (single source of truth for
  vertical rhythm; replaces ad-hoc rem values throughout the .svelte files)
- status pill tokens: --color-status-{open,closed}-{bg,fg} (dark-mode aware,
  closed pulls from the same warning palette as .fb-banner--closed)
- .fb-shell.fb-page-narrow + .fb-page-center for vertical-centred narrow
  shells (landing + login)
- .fb-back-link — quiet text-only back-link, replaces the chip-style button
- .fb-inline-error — quieter alternative to .fb-banner--error
- .fb-toggle / .fb-toggle__{text,label,hint} — label-left + checkbox-right
  boolean fields (no UI library)
- .fb-status-pill / .fb-status-pill__dot / --open / --closed — clickable
  pill that toggles status
- .fb-menu / .fb-menu__{btn,panel,item,divider,item--danger} — native
  <details>/<summary>-based ⋯ menu, no JS framework needed
- .fb-card / .fb-card__{head,title,actions,meta} + .fb-card-grid — minimalist
  card on the gradient page bg, no border + shadow stack, generous padding
- .fb-empty — generous empty state
- .fb-share-strip / __url / __placeholder / __replace — inline header strip
  for the detail page, replaces the standalone Share section
- .fb-closed-line — neutral muted closed-state line for /f/[slug]
- .fb-segment / __btn / __btn--active — small segmented control matching
  .fb-tabs (for inline Visual/JSON toggle on Edit tab)
- .fb-detail-head / __title / __actions, .fb-tab-body, .fb-version-note,
  .fb-save-row — detail-page header + tab-body layout primitives

Also normalises:

- .fb-section margin-bottom 1.75rem → var(--space-7) (≈ 2.5rem)
- focus-ring opacity 0.15 / 0.25 → 0.2 across .fb-input + .fb-btn for a
  single consistent focus treatment

No structural .svelte changes here — only CSS additions and three numeric
edits. Existing pages continue to render exactly as before; the per-screen
commits that follow consume these classes.
2026-05-06 12:25:29 +02:00
mAi
301cec817a docs: minimalist UI redesign proposal
Per-screen audit + 6 design principles + per-screen mockups + commit-by-commit
implementation plan + 7 open questions.

Boldest moves: collapse the 5-button-per-row admin list into a hover-revealed
⋯ menu with clickable status pill; fold the standalone Share section into the
detail-page header as an inline link strip; drop the JSON-questions textarea
from /new behind a <details> disclosure so the common path reads as four
inputs and a button.

No code touched — design only. Awaiting m's go before coder shift.
2026-05-06 12:16:18 +02:00
mAi
3d03ee0c85 Merge mai/hermes/fdbck-shlink-short-link: shlink integration + admin Share section 2026-05-05 23:15:26 +02:00
mAi
696b796383 mAi: #2 - admin Share section + env-var docs
Self-contained "Share" section on the admin detail page. When no short URL
exists yet: shows an optional custom-slug input + "Create short link"
button. When one exists: shows the URL with Copy + Open buttons and a
collapsed "Replace" form for picking a new slug.

Append-only — does not touch existing buttons, the icon system, or
feedback.css; uses inline styles + existing fb-* classes only, so it stays
out of dokploy's parallel button-system refactor.

.env.example documents SHLINK_URL + SHLINK_API_KEY (must be copied from the
flexsiebels.de Dokploy app config to fdbck.msbls.de before this works in
prod).

Refs m/fdbck#2.
2026-05-05 23:13:13 +02:00
mAi
c5d0b2ae60 mAi: #2 - shlink server: helper + share endpoint + schema
Port flexsiebels' shlink REST v3 wrapper for short-link sharing of feedback
forms. New helper `src/lib/server/shlink.ts` reads SHLINK_URL (default
https://msbls.de) + SHLINK_API_KEY from env. New auth-gated
`POST /api/admin/feedback/<id>/share` builds the long URL from
PUBLIC_SITE_URL + instance.slug, calls shlink, persists shortUrl/shortCode
on feedback_instances, and returns the updated row. Adds ShareCreateSchema
(zod) for the request body and extends FeedbackInstance with the new
columns.

DB columns short_url + short_code added via Supabase migration
fdbck_feedback_instances_add_short_url (both TEXT NULL).

Refs m/fdbck#2.
2026-05-05 23:13:05 +02:00
m
e53b468dea Merge mai/dokploy/button-system: button variants + icons + Inter font 2026-05-05 23:12:18 +02:00
m
527fd57a72 button system: variants + icons + consistent sizing + Inter font
- Icon.svelte: inline lucide SVGs (apache-licensed paths copied directly,
  no npm dep) — edit, copy, external-link, lock, unlock, trash, plus, eye,
  eye-off, check, x, arrow-left, arrow-right, download.

- Refactored .fb-btn system in feedback.css:
  * Base = primary green, fixed 2.5rem height, gap for icons, Inter font.
  * .fb-btn--secondary: tinted primary-light surface that fills on hover.
  * .fb-btn--ghost: subtle gray bg with border (NOT bare white) — addresses
    m's "no background color" complaint. Dark-mode override included.
  * .fb-btn--danger: red, lifts shadow on hover.
  * .fb-btn--sm / --lg: 2rem / 3rem heights with proportional padding.
  * .fb-btn--icon: square icon-only variant.
  * Focus-visible ring via box-shadow on the green primary tint.

- app.html: preconnect + load Inter (400/500/600/700) from Google Fonts.
  .fb-page now stacks 'Inter' first.

- Applied icons + the right variant on every button across admin chrome
  + login per the brief table:
  * list row: Edit (secondary, edit), Copy link (ghost, copy), Open
    (ghost, external-link), Close/Reopen (ghost, lock/unlock),
    Delete (danger, trash) — all --sm so the row breathes.
  * list header CTA "+ New form" (primary, plus).
  * /admin/feedback/new: back link (ghost --sm, arrow-left), Insert
    sample (secondary --sm, plus), Create form submit (primary, check).
  * detail header: back link, Copy link, Preview, Close/Reopen
    (secondary), CSV/JSON exports (ghost, download), Delete (danger,
    trash) — all --sm.
  * detail chat: hide/show buttons promoted to .fb-btn--ghost --sm
    with eye/eye-off icons; killed the inline-styled bare button.
  * detail edit tab: Visual/JSON toggle now flips between --secondary
    (active) and --ghost (inactive) with no inline style overrides;
    Save (primary, check); "+ Add questions" (secondary --sm, plus).
  * login: Sign in keeps primary, gains arrow-right hint on the right.

- FormBuilder: Add-option / type-add buttons reduced to --sm + plus
  icon. Remove-question / remove-option icon-buttons get the trash/x
  lucide SVG. Killed the redundant CSS overrides on these
  (.fb-builder__add-option / .fb-builder__add .fb-btn) since
  .fb-btn--sm now does the sizing.

Acceptance: no inline button overrides remain on .fb-btn instances; all
admin row heights are consistent (2rem); ghost buttons have the tinted
surface; dark mode handled via @media block.
2026-05-05 23:12:12 +02:00
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