Every shopping item now gets a visual like in Bring: a product emoji on
a softly tinted category tile.
- ItemVisuals.kt: pure client-side classifier. German dictionary
(~180 entries) maps item names to emoji + one of 13 categories
(produce, drinks, bakery, dairy, meat&fish, frozen, pantry, sweets,
household, drugstore, baby, pet, other), each with an accent color.
Longest-keyword-first matching so compounds resolve to their most
specific entry (Wassermelone is produce, not a drink); juice
compounds (Apfelsaft, Orangensaft, ...) are listed explicitly because
their fruit prefix is longer than 'saft'. Unknown items fall back to
the generic cart on neutral grey. Works fully offline, nothing leaves
the device.
- ListDetailScreen: emoji tile in every item row, emoji in the
autocomplete suggestions dropdown, and a live emoji preview as the
text field's leading icon while typing.
- ItemVisualsTest: longest-match priority, case/whitespace
insensitivity, fallbacks, category coverage.
Verified: assembleDebug + unit tests green (JDK 21).
- AuthScreen.kt: FocusManager is imported from androidx.compose.ui.focus,
not androidx.compose.ui.platform (only LocalFocusManager lives there).
The fully-qualified platform.FocusManager parameter types therefore
failed to resolve, cascading into the clearFocus() errors.
- MainScreen.kt: MitbringslTheme gained a required sessionManager
parameter in 729865b; the leftover template preview now passes
SessionManager(LocalContext.current).
Verified: ./gradlew compileDebugKotlin, assembleDebug and test all green.
Note: local builds must use JDK 21 (JAVA_HOME=/usr/lib/jvm/java-21-openjdk);
the system default JDK 26 breaks AGP's JdkImageTransform (jlink) on
android-36's core-for-system-modules.jar.
Document the four features that landed on main in b44bc8c..85c790e:
- AGENTS.md: post-MVP section now covers server-driven auth discovery
(GET /api/config, AUTH_PASSWORD_ENABLED 403 enforcement,
OIDC_GENERIC_DISPLAY_NAME), the settings screen with GET/PUT /api/me,
and the critical Android sync/data-safety fixes (pull-all-lists,
client HLC tick per server op, no destructive migration, ProGuard
rules). Repo structure updated (httpapi config.go/me.go, ui/settings),
roadmap entries added, open-points list extended with test backlog for
the new endpoints.
- API.md: new sections for GET /api/config (public) and GET/PUT /api/me;
403 responses documented for register/login when password auth is off.
- SYNC.md: client pull loop (every tracked list, op_log pruning) and the
client HLC discipline (tick per incoming server op) that keeps LWW
correct across devices with skewed clocks.
- README: highlights for auth discovery/OIDC-only mode and the settings
screen.
Integration tests for the invite/join/membership feature that shipped
without any coverage:
- internal/store/liststore_test.go: CreateList adds owner as member with
invite code, GetLists returns owned+joined but not foreign lists,
GetList access control (owner/member yes, stranger and soft-deleted no),
JoinByInviteCode normalization/idempotency/role-keeping, lazy invite
code generation. Runs against TEST_DATABASE_URL, skips otherwise.
- internal/httpapi/api_test.go: full E2E over the real router — register,
create list (code in response), invite endpoint, join (lowercase),
cross-member op push/pull sync, stranger gets 404 on every list
endpoint, invalid code 400, idempotent re-join, and 401 gating of all
protected routes.
- lists.go Invite handler: store errors now map through apiError, so
non-members get 404 instead of 400 (consistent with Get/Push/Pull).
Docs updated to the actual post-MVP state: AGENTS.md (post-MVP features,
repo structure, roadmap with open points like join rate limiting),
API.md (join/invite endpoints, invite_code fields, membership rules),
SYNC.md (shared lists section), README (local-only default, sharing,
integration test recipe).
- settings.gradle.kts: comment out the foojay-resolver-convention plugin.
It tries to auto-download JDK toolchains from api.foojay.io, which fails
with 503 behind restrictive proxies. Re-enable at home if you want
auto-provisioning.
- app/build.gradle.kts: comment out jvmToolchain(17) for the same reason
(triggers toolchain resolution -> foojay download). The build JDK 21
plus compileOptions(source/target 17) achieves the same result locally.
- gradle-wrapper.properties: bump to gradle-9.6.1-all (matches the
manually provided distribution).
- .gitignore: ignore .ssl-work/ (local corporate-CA truststore).
Backend:
- New GET/PUT /api/me endpoint to read and update the authenticated
user's profile (currently display_name). UserStore.UpdateDisplayName.
- Wired into the authed router.
App:
- New SettingsScreen + SettingsViewModel with five sections:
* Account & Sync: login status, email, server URL, logout, connect.
* Profile: edit display name (pushed to PUT /api/me when logged in).
* Appearance: System / Light / Dark theme switch, persisted in
SessionManager and applied via MitbringslTheme(sessionManager).
* Data: 'Reset local data' wipes Room tables (server data kept).
* About: version, 'Developed by Janik Dietz', and credits to
GLM-5.2 + Gemini 3.6 Flash.
- Theme.kt now reads the user's theme preference (StateFlow) instead
of only the system default; MainActivity passes SessionManager in.
- Navigation: new SettingsNavKey; settings gear icon in ListsScreen
top bar; Settings links back to the Sync/Account screen.
- DTOs/API: UpdateMeRequestDto + getMe()/updateMe() for /api/me.
Backend:
- New AUTH_PASSWORD_ENABLED flag (default true). When false, email/password
registration and login return 403; the server enforces OIDC-only login.
- New OIDC_GENERIC_DISPLAY_NAME so the app can show 'Authentik'/'Keycloak'
instead of a generic 'OIDC' label.
- New public endpoint GET /api/config returns which auth methods the
server offers (password_enabled + per-provider OIDC capabilities).
No auth required, so the login screen can query it before logging in.
- .env.example and docker-compose.yml expose the new env vars.
App:
- DTOs + MitbringslApi.getServerConfig() for /api/config.
- AuthViewModel: new 'connect' flow. The user enters the server URL,
taps 'Verbinden', and the app fetches /api/config. The returned
ServerAuthConfig drives which login options are shown:
* password-only -> email/password form
* OIDC-only -> OIDC token form
* both -> toggle between the two
If the server offers no method, a clear error is shown.
- AuthScreen: split into ConnectView (server URL) and LoginView (the
login form matching the server's capabilities). The mode toggle only
appears when the server offers more than one method.
Sync engine (critical):
- SyncWorker now pulls server ops for EVERY tracked list, not just
lists with pending local outbox ops. Previously remote edits on
'quiet' lists (incl. shared/joined lists) never arrived.
- SyncWorker advances the local HLC with each incoming server op
(tick(op.hlcTs)) so LWW is correct across devices with skewed
clocks; previously a fast-clock device permanently won conflicts
and a slow-clock device's own edits were silently rejected.
- Added missing 'list_create' branch in applyServerOpProjection.
- Use server hlc_ts for createdAt/updatedAt in projections so lists
and items keep a stable order instead of jumping by sync time.
- Prune old synced op_log rows (deleteOldSynced) to bound growth.
- Added ListDao.getAllListIds() one-shot query for the pull loop.
Build / runtime (critical):
- Added proguard-rules.pro with keep rules for kotlinx.serialization
serializers and Retrofit interfaces; release builds with R8 would
otherwise crash with SerializationException on the first API call.
- Added network_security_config.xml allowing cleartext only to
10.0.2.2/localhost so the debug build can reach the local backend
(blocked by default on Android 9+).
- Manifest: disable default WorkManager initializer so Hilt's
HiltWorkerFactory is used (otherwise SyncWorker can fail to
instantiate); added ACCESS_NETWORK_STATE permission.
Data safety (critical/major):
- Removed fallbackToDestructiveMigration from DatabaseModule: in a
local-first app a destructive migration on schema bump would wipe
the source of truth. Provide explicit Migrations instead.
- ShoppingRepository: wrap every local projection write + op_log
insert in db.withTransaction{} so a crash between them can no
longer silently lose a pending sync op.
- ShoppingRepository: replace manual JSON string concatenation with
kotlinx.serialization buildJsonObject; the old escapeJson did not
handle tab/CR/control chars, producing malformed op payloads.
- Persist device clientId and clientSeq counter in SessionManager so
they survive process restarts (idempotency stays stable per device).
- Trigger immediate + periodic sync after login/register/OIDC so
users see their lists without waiting up to 15 minutes.
.gitignore: ignore desktop.ini and backend/.testbin.
- ListDetailScreen: hide Share action icon when not connected to a server (isLoggedIn == false)
- ListsScreen: show informative prompt dialog if user attempts to join a list without active server connection
- ListDetailViewModel: expose sessionManager to check auth state cleanly
- ADB reinstall & launch verified ✅
- Generate high-res 3D shopping bag icon with checkmark badge
- Populate mipmap densities (mdpi, hdpi, xhdpi, xxhdpi, xxxhdpi)
- Configure adaptive icon XML & dark blue background color
- Re-install via ADB verified ✅
- SessionManager: return stable local_user ID when not logged in
- Navigation: start directly at ListsNavKey without requiring login
- ListsScreen: add 'Sync / Account' chip in top bar; lists work 100% locally by default
- AuthScreen: update text & add 'Zurück zu meinen Listen' button for returning to local mode
- ADB reinstall & launch verified ✅
Argon2id password hashing (PHC format, self-encoded/decoded without an
external lib) with constant-time verification, UserStore (create/get by
email and id) and SessionStore (opaque crypto/rand tokens, SHA-256 hashed
in DB, create/lookup/revoke, last_seen_at bump on lookup).
HTTP layer: Register/Login/Logout handlers + RequireAuth middleware.
Login uses a dummy-hash path so unknown-email and wrong-password yield the
same timing/shape, narrowing user enumeration. Tokens accepted via Bearer
header (native clients) or session cookie (HttpOnly, SameSite=Lax).
Routes wired in api.go: POST /auth/register, /auth/login, /auth/logout.
Verified with go test, go vet and an end-to-end smoke test against a real
PostgreSQL container (register/login/logout/duplicate/short-pw/wrong-pw).
OIDC (Phase B part 2) follows next; the issueSession helper is reused.