Tests:
- packages/feedback/src/avatar.test.ts — 10 unit tests (determinism,
mirror-symmetry, color contrast, padding-resilience, pseudonym-
integration, density-sanity).
- services/mana-analytics/src/services/feedback-redact.test.ts —
9 privacy-boundary tests verifying:
* anonymous path NEVER includes realName, even when author opted in
* auth path NEVER includes realName when author opted OUT
* realName only when (opted-in AND auth-path) — both gates required
* userId / deviceInfo / voteCount stripped from output
Plan-Doc:
- docs/plans/feedback-rewards-and-identity.md status → shipped (3.A,
3.B, 3.C, 3.F live; 3.D, 3.E open) mit Commit-Hashes.
Service-Layer minor: REWARD-const + redact als __TEST__-Export
publik gemacht (nur fürs Testen, kein Verhaltensänderung).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Macht aus den Pseudonymen echte Charaktere ohne Klarnamen-Zwang.
Pixel-Identicon-Avatar (3.C.2):
- generateAvatarSvg(displayHash) — pure-function, deterministisch.
5×5 left-mirrored Identicon mit HSL-Foreground/Background aus dem
Hash. Inline-SVG, kein Storage, kein img-load-Flicker.
- <EulenAvatar> Component im Package, in ItemCard neben dem Pseudonym.
Klarname-Toggle (3.C.1):
- auth.users + community_show_real_name boolean (default off, opt-in).
- PATCH /api/v1/me/profile akzeptiert communityShowRealName.
- mana-analytics LEFT JOINs auth.users → bei opt-in liefert auth-
required /public + /me/reacted Endpoints zusätzlich realName.
- Anonymous /api/v1/public/feedback/* zeigt realName NIE — auch nicht
wenn opted-in. Public-Mirror bleibt für SEO + Privacy safe.
- Migration 008_community_identity.sql lokal + prod eingespielt.
Karma-System (3.C.3):
- auth.users + community_karma int. toggleReaction increment/decrement
am Author-User (Self-Reactions zählen nicht — kein Self-Farming).
- KARMA_THRESHOLDS + tierFromKarma() im Package: Bronze (0-9) /
Silver (10-49) / Gold (50-199) / Platin (200+).
- ItemCard zeigt Tier-Dot neben dem Pseudonym, Title-Tooltip mit
Karma-Zahl. Floor-clamped at 0.
Eulen-Profil (3.C.4):
- GET /api/v1/public/feedback/eule/{hash} — alle public-Posts dieser
Eule + aggregiertes Karma. SHA256-Format-Validation.
- /community/eule/[hash] Public-SSR-Route mit Avatar-Hero, Tier-Badge,
Karma-Counter, Post-Liste. Author-Klick im ItemCard navigiert hin.
- publicFeedbackService.getEulenProfile() im Package.
PublicFeedbackItem erweitert um displayHash (public Pseudonym-ID,
SHA256 ist one-way → safe to expose) + karma + optional realName.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Schließt den Loop zwischen Submit und Ship. User kriegt jetzt:
- Toast beim nächsten App-Start, wenn ein eigener oder unterstützter
Wisch ›planned/in_progress/completed/declined‹ wurde
- /profile/my-wishes als persönliche Roadmap mit drei Tabs:
Eigene · Unterstützt · Inbox
Server (mana-analytics):
- Neue Tabelle feedback_notifications mit ON DELETE CASCADE auf
user_feedback. Migration 0004 lokal + prod eingespielt.
- adminUpdate enqueued bei jeder Status-Transition Author-
Notifications. AdminResponse-Edits feuern eine eigene
'admin_response'-Notify. tryGrantShipBonus hängt zusätzlich
Reactioner-Notifications dran (›Dein Like ist gelandet, +25 Mana‹).
- Endpoints:
GET /api/v1/feedback/me/notifications?unread_only=true&limit=N
POST /api/v1/feedback/me/notifications/:id/read
POST /api/v1/feedback/me/notifications/read-all
GET /api/v1/feedback/me/reacted (für die My-Wishes-Page)
Package (@mana/feedback):
- FeedbackNotification + NotificationKind types exportiert
- service.getNotifications/markNotificationRead/markAllNotificationsRead
- service.getMyReactedItems
Web:
- lib/notifications/feedback-toaster.svelte.ts: Boot-Pull + 60s-Poll,
rendert unread-notifications via toast-store, markiert sofort read.
In (app)/+layout.svelte's authReady-Hook gestartet/gestoppt.
- /profile/my-wishes: Tab-View über getMyFeedback + getMyReactedItems
+ getNotifications. Tabs zeigen Counter-Badges, unread-Badge in der
Inbox-Sektion. ›Alle als gelesen markieren‹-Action vorhanden.
Pre-launch saubere Lösung — kein Polling-Spam (60s), Mark-Read direkt
nach Toast-Display, fail-soft an mehreren Stellen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mana/feedback wird zur Pflege-SSOT für Public-Community-Hub.
- PublicFeedbackItem-Typ: anonymisiertes Item, das nur display_name +
reactions + status führt — kein userId, displayHash, deviceInfo.
- ReactionEmoji ('👍' '❤️' '🚀' '🤔' '🎉') + REACTION_LABELS mit DE-Labels.
- CreateFeedbackInput erweitert um moduleContext + parentId. Reactions
+ score auf Feedback-Type optional gemacht.
- Service-Split:
createFeedbackService — auth-required Submit/React/Manage,
getPublicFeed (auth-enriched mit myReactions)
createPublicFeedbackService — anonymous, SSR-only, getFeed/getItem.
toggleReaction(emoji) statt vote/unvote (legacy-Shims bleiben für
back-compat zu vote → '👍'-Toggle).
- ReactionBar.svelte: Slack-Style emoji-row mit Active-Highlighting für
myReactions, ReadOnly-Mode für Public-SSR. Auto-disabled-Tooltip.
- index.ts re-exportiert die neuen Typen + ReactionBar; FeedbackVote
rausgeschmissen (durch FeedbackReactions im Server-Schema ersetzt).
FeedbackCard + FeedbackPage minimal angepasst, damit svelte-check
clean bleibt — die Legacy-Komponenten bleiben funktional, werden aber
in Phase 3 zu @mana/feedback's neuen Modul-Views ausgemistet.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Macht @mana/feedback zur SSOT für alle Nutzer-Feedback-Categories und
-Status — Voraussetzung dafür, dass Onboarding-Wishes, NPS, Churn-Feedback
etc. künftig dort landen.
- Status-Enum: DB-Werte umbenannt new/reviewed/done/rejected →
submitted/under_review/completed/declined (Package gewinnt). PG≥10
ALTER TYPE … RENAME VALUE ist non-destructive.
- Category 'praise' ins Package aufgenommen (war nur in DB).
- Category 'onboarding-wish' neu in Package + DB für den Wish-Step.
- Default status in DB: 'new' → 'submitted'.
- CreateFeedbackInput.isPublic optional → Service reicht durch, default
bleibt true; private Categories wie onboarding-wish setzen false.
- Schema-Datei mit SSOT-Kommentar versehen, der Drift in Zukunft verhindert.
Hand-authored Migration unter services/mana-analytics/drizzle/0001_*.sql
weil drizzle-kit push Enum-Werte nicht zuverlässig umbenennt. Manuell
einspielen vor nächstem db:push:
psql "\$DATABASE_URL" -f services/mana-analytics/drizzle/0001_align-feedback-enums.sql
Plan in docs/plans/feedback-hub.md (Phase 0–4); Phase 0 + 1 jetzt, 2-4
deferred.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five unrelated packages each had a few imports pointing at the wrong
file or missing from their public surface. Grouped because none of
the individual fixes warrants its own commit and they all unblock
the same downstream consumer (apps/mana/apps/web type-check).
packages/help
- HelpPage.svelte: `'../types.js'` and `'./content'` for
HelpPageProps/HelpSection/SearchResult — neither path exists.
Real homes are `../ui-types` (props) and `../search-types`
(search shapes). Fix the imports.
- HelpSearch.svelte: same `'../content'` typo for SearchResult →
`'../search-types'`.
- translations.ts: `'./types.js'` for HelpPageTranslations →
`'./ui-types'`.
- ui-types.ts: was importing SearchResult from `'./content'` but
that module only exports content shapes. Split into two imports
so HelpContent stays from content.ts and SearchResult comes from
search-types.ts.
packages/feedback
- FeedbackPage.svelte: imported `Feedback` and `CreateFeedbackInput`
from `'./createFeedbackService'` but the service module only
exports the service factory. Real homes are `'./feedback'`
(Feedback) and `'./api'` (CreateFeedbackInput).
- FeedbackForm.svelte: same `'./feedback'` typo for
CreateFeedbackInput → `'./api'`.
packages/subscriptions
- UsageCard / CostCard / pages/SubscriptionPage: all imported
UsageData / CostItem from `'./plans'` but those types live in
`'./usage'`. SubscriptionPage additionally had a relative-path
bug — it's at `src/pages/`, not `src/`, so `./plans` resolved
to `pages/plans` (nonexistent). Now imports `'../plans'` for
plan types and `'../usage'` for usage/cost types.
packages/shared-ui
- index.ts: re-exports the QuickInputItem family from
`./quick-input` but had forgotten `HighlightPattern`. Added.
Apps that build their own InputBar pattern config (e.g.
mana/web/src/lib/quick-input/types.ts) need it as a public type.
- PillNavigation.svelte: imported `SpotlightAction` and
`ContentSearcher` from `./GlobalSpotlight.svelte` (a Svelte
component file), which only re-exports the default. Both types
live in `./types`. Move them to the existing types-import
block; the GlobalSpotlight import becomes a plain default.
packages/shared-auth-ui
- stores/createAuthStore.svelte.ts: imported AuthServiceAdapter /
AuthResult / BaseUser from `'./types'` (nonexistent — the file
is `'./store-types'`).
Net: -23 type errors. Zero behavior change.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Merged shared-feedback-types + shared-feedback-service + shared-feedback-ui
into a single @manacore/feedback package. Updated imports in all 21 apps.
Before: 3 packages (types, service, ui) with cross-dependencies
After: 1 package with direct imports, no circular refs
Note: ESLint warnings from pre-existing unused vars in chat/mukke
servers are unrelated to this change.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>