Build the prototype: iPhone-distributor back-office — stock, forecasts, dashboard, one app with multiple tabs #1
Reference in New Issue
Block a user
No description provided.
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
m, 2026-07-26 (PWA voice): "bau mal einen Prototyp für eine kleine App. Wir tun so, als wären wir ein Handy-Distributor fürs iPhone und wir möchten ein paar Funktionen einbauen. Kleine Datenbank im Hintergrund, du kannst bei uns Supabase nehmen und dann ein Webfrontend. Wir wollen Forecasts und aktuelle Bestände und insbesondere ein Dashboard. Alles in einer App, multiple Tabs."
This is a prototype. Demonstrable beats complete. It is a fictional business used to exercise the stack — there is no real distributor, no real data, and no user waiting on correctness. Build something m can click through.
Read this first — the stack is already decided
~/dev/web/docs/pwa-baseline.md(506 lines, repom/mWeb) is the canonical baseline and records m's resolved choices. Do not re-litigate them and do not invent a stack:db/NNN_*.sql, applied at server startup/api/<resource>[data-theme="dark"]), full design-system tokens in thevariables.cssshapemdistri-<key>"Multiple tabs" = the sidebar/bottom-nav pattern from the baseline. That mechanism already exists in the design system; use it rather than building a tab bar.
Also worth reading before starting, for the shape of a comparable app in this repo family:
~/dev/web/fdbckand~/dev/web/postcards.Scope — three tabs
Bestände (current stock) — the inventory view. Models × storage × colour × condition, quantity on hand, warehouse location, purchase price, sale price. Filterable and sortable. This is the table the rest of the app hangs off.
Forecasts — projected demand and stock-out risk per SKU over a horizon (weeks). A prototype forecast is fine and should be visibly simple: a documented naive method (moving average over historical sales, plus a seasonality nudge) with the method named in the UI. Do not present a made-up number as if a model produced it — if it is a moving average, the UI says so.
Dashboard — m called this out specifically ("insbesondere ein Dashboard"), so it is the centrepiece, not an afterthought. Total stock value, units on hand, stock-out risks, top movers, incoming vs outgoing, a trend chart or two. It should be the first thing that opens and it should look good.
Seed data
Generate plausible fake data — real iPhone model names and storage tiers, sensible EUR price points, several months of synthetic sales history so the forecasts have something to chew on. Enough rows that the dashboard looks alive (hundreds, not five). Ship it as a seed migration so the prototype is reproducible from an empty schema.
Infrastructure — reuse the documented path
mdistrion the shared msupabase. Connect aspostgresvia the Supavisor pooler (100.99.98.203:6789), not assupabase_admin— a schema created by the wrong role causes a permission-denied crash-loop at container start. That trap is recorded and has cost a debugging session before.postcards(application.create→saveGiteaProvider→saveBuildTypedockerfile →saveEnvironment→domain.create→application.deploy). The/mai-dokployskill has the endpoint reference.mdistri.prev.msbls.deon the existing wildcard, so m can look at it before anything else is decided. No domain purchase, no money.Ask, don't guess
If a product decision genuinely changes the build — how much of the distributor fiction to model (single warehouse or several? B2B customers or none?), what the forecast horizon should be — use
AskUserQuestion, one round, several questions together. m prefers one form over a chain of small questions. But do not block on cosmetics; pick sensible defaults and say what you picked.Acceptance
mdistri.prev.msbls.deloads, three tabs work, light and dark both look intentional.mdistrischema, not hardcoded fixtures in the frontend.Prototyp steht — Preview: https://mdistri.prev.msbls.de
Drei Tabs auf dem pwa-baseline-Stack (Svelte 5 + SvelteKit 2 + Bun + adapter-node), eigenes
mdistri-Schema auf msupabase. Branchmai/knuth/issue-1-iphone, nicht nach main gemerged — das macht der Head.Commits
9cfca51— feat: iPhone-distributor back-office prototypeaaddcd2— fix: Service-Worker + Deploy-NotizenWas drin ist
Dashboard (öffnet zuerst): Hero-Zahl Umsatz letzte 4 Wochen mit Delta, KPI-Kacheln (Lagerwert, offene Aufträge, Stock-out-Risiko, erwarteter Wareneingang), Absatzchart mit 26 Wochen Ist und 8 Wochen Prognose auf derselben Achse, Top-Movers, Stock-out-Risiken, Bestand nach Standort, Umsatz nach Modellreihe, Top-Kunden, Wareneingang.
Bestände: 88 SKUs über 3 Standorte, filter- und sortierbar (Modellreihe, Zustand, Standort, nur Risiko-SKUs), Meldebestand-Markierung, Reichweite je SKU.
Forecasts: Prognose je SKU über 8 Wochen, Detailchart + Wochentabelle mit dem Saisonindex je Woche, Saisonindex-Panel, Sparklines in der Liste.
Dazu Sidebar/BottomNav nach Baseline, Light+Dark mit Toggle, CMD+K-Palette (Seiten + SKUs), PWA-Shell.
Zu den Forecast-Zahlen
Das Verfahren steht wörtlich im UI, bevor die erste Zahl kommt, inklusive Grenzen — nichts wird als Modellergebnis ausgegeben:
Schritt 2 ist der Punkt, an dem ich zweimal nachgebessert habe. Ohne die Saisonbereinigung zählt das Verfahren die Saison doppelt (das Fenster liegt schon im schwachen Monat) und die Kurve fällt an der Grenze Ist→Prognose um die Hälfte ab. Und ein Monat mit nur einer beobachteten Woche — hier der September mit der iPhone-17-Launchwoche — erzeugte einen Index am oberen Anschlag und damit einen Sprung, der wie ein Fehler aussah. Beides ist in
src/lib/forecast.tsgelöst und mit Tests festgenagelt.Genannte Grenzen: kein Trend, keine Aktionen, keine Lieferengpässe; der Index vermischt Saisonalität mit Produkt-Launches; bei unter einem Jahr Historie haben mehrere Monate keinen belastbaren Index.
Seed-Daten
Deterministisch (md5-abgeleitet, kein
random()), reproduzierbar aus leerem Schema: 88 SKUs mit echten Modellnamen und aus der UVP abgeleiteten Preisen (iPhone 15 bis 17 Pro Max, neu + refurbished), 3 Lager, 14 B2B-Kunden, 2.907 Aufträge / 7.413 Positionen über 42 Wochen, aktuelle Bestände, 24 offene Bestellungen. Die Historie trägt eine echte Saisonkurve (Dezember-Peak, Januar-Tal), damit die Forecasts etwas zu rechnen haben.Produktentscheidungen
AskUserQuestionwar nicht verfügbar (kein Mensch am Pane), die Fragen liegen als Report beim Head. Defaults, die ich gewählt habe — bitte gegenlesen:Verifiziert
bun run checksauber (340 Dateien, 0 Fehler), 34 Unit-Tests grünZwei Deploy-Fallen, die Zeit gekostet haben (jetzt in CLAUDE.md/README)
DATABASE_URLmitmriver.horse-ayu.ts.netstirbt beim Start mitgetaddrinfo ENOTFOUND— Container im Dokploy-Overlay-Netz lösen*.ts.netnicht auf. Es muss die Tailscale-IP100.99.98.203:6789sein, so wie projax es auch macht. (Die Rollen-Falle aus dem Issue habe ich vermieden: verbunden alspostgres.)*.prev.msbls.degehört dem onepager-Wildcard.prev-msbls-wildcard.yml(mAi#320) routet jedes<slug>.prev.msbls.demitpriority: 50auf das Caddy-Backend. Eine über die Dokploy-UI angelegte Domain erzeugt einen exaktenHost()-Router ohne Priority (~28) und verliert — die Anfrage landet bei Caddy und wird mit 444 beantwortet. Der Kommentar in der Wildcard-Datei behauptet, exakte Host-Router seien nicht betroffen; für einen Host unter dem Wildcard stimmt das nicht. Gelöst wie bei postcards: handgelegte Dateideploy/traefik/mdistri-prev.ymlmitpriority: 100, Domain bewusst nicht in der Dokploy-UI.Nebenbei aufgefallen: Traefik rendert jede Dynamic-Datei als Go-Template, bevor das YAML geparst wird. Ein
docker --format-Beispiel mit doppelten geschweiften Klammern in einem#-Kommentar reicht, um nicht nur die eigene Datei zu killen, sondern den Watcher-Callback für das gesamte Dynamic-Verzeichnis. Ist im Header der Datei dokumentiert.Gefundener Bug, der nichts mit dem Deploy zu tun hatte
Der erste Service Worker fing jeden Same-Origin-GET ab und löste
respondWithim Fehlerfall mit dem Rückgabewert voncaches.match()auf — der bei einem Missundefinedist. Ergebnis: sporadisch scheiternde_app/immutable/*-Chunks undFailed to fetch dynamically imported module, während Traefik im selben Zeitraum ausschließlich 200er loggte. Der Worker fasst jetzt nur noch Navigationen an (gehashte Build-Assets erledigt der HTTP-Cache) und gibt auf jedem Pfad eine echte Response zurück. Mit Playwright gegengeprüft: Service Worker blockiert → 0 Fehler bei 66 Responses; erlaubt → Fehler auf den Chunks. Nach dem Fix vier Durchläufe sauber.Offen / nicht gemacht
giteaBranchder Dokploy-App (4WUbPQkqCoU5wIvR3VVvc) aufmainumgestellt werden.--reset, zeigt sie eine halbjährig alte „letzte Woche". Für einen Prototyp bewusst so; wenn das stört, ist ein Reseed-Job die Lösung.