Lead with Batch on the home page, drop the product changelog, add a domain note, and stop reporting success when SMTP fails. Co-authored-by: Cursor <cursoragent@cursor.com>
161 lines
9.4 KiB
TypeScript
161 lines
9.4 KiB
TypeScript
import type { Locale } from "@/i18n/routes";
|
|
|
|
export type ProjectSlug = "w-make-batch" | "batchmaker-studio";
|
|
|
|
export type ProjectImage = {
|
|
src: string;
|
|
de: string;
|
|
en: string;
|
|
};
|
|
|
|
export type LocalizedProject = {
|
|
slug: ProjectSlug;
|
|
number: string;
|
|
href?: string;
|
|
images: ProjectImage[];
|
|
status: string;
|
|
label: string;
|
|
title: string;
|
|
summary: string;
|
|
stance: string;
|
|
stack: string;
|
|
role: string;
|
|
situation: string;
|
|
decisions: string[];
|
|
outcome: string;
|
|
proof: string;
|
|
lesson: string;
|
|
stages: { title: string; text: string }[];
|
|
};
|
|
|
|
const projects: Record<ProjectSlug, { number: string; href?: string; images: ProjectImage[]; de: Omit<LocalizedProject, "slug" | "number" | "href" | "images">; en: Omit<LocalizedProject, "slug" | "number" | "href" | "images"> }> = {
|
|
"w-make-batch": {
|
|
number: "01",
|
|
href: "https://batch.w-make.com",
|
|
images: [
|
|
{ src: "/portfolio/batch-start.jpg", de: "Dashboard: Schicht, Alarme und der Weg in Satzzettel und Protokoll.", en: "Dashboard: shift, alerts, and the path into tickets and the daily log." },
|
|
{ src: "/portfolio/batch-satzzettel.jpg", de: "Satzzettel mit Rezept, Zielsatz und sichtbarer Redox-Lage.", en: "Batch ticket with recipe, target weight, and visible redox state." },
|
|
{ src: "/portfolio/batch-rezepte.jpg", de: "Rezeptarbeitsplatz: Versionen, Temperatur und Kosten als Stammdaten.", en: "Recipe workspace: versions, temperature, and cost as master data." },
|
|
{ src: "/portfolio/batch-tagesprotokoll.jpg", de: "Tagesprotokoll mit Schichtübergabe und Tagesabschluss.", en: "Daily log with shift handover and day close." },
|
|
],
|
|
de: {
|
|
status: "Öffentliche Produkt-Demo",
|
|
label: "Industrie-Software · Produktentwicklung",
|
|
title: "W-Make Batch",
|
|
summary: "Batchmanagement für die Behälterglas-Produktion — vom Satzzettel bis zum Tagesprotokoll.",
|
|
stance: "Das Produktionsprodukt. Eine öffentliche Demo zeigt den Ablauf, nicht den Kundenstand.",
|
|
stack: "Node.js · Express · SQLite · Web Components · native Tests · Playwright",
|
|
role: "Domänenmodell, Full-Stack-Entwicklung und technische Qualität.",
|
|
situation: "Ein Produktionsprozess lebt von Rezepten, Materialverbrauch, Silos, Schichten und Berechnungen. Wird das als lose Sammlung von Masken gebaut, gehen fachliche Zusammenhänge verloren — und Buchungen werden zu editierbaren Tabellenzeilen.",
|
|
decisions: [
|
|
"Fachliche Buchungen sind Zustände, keine CRUD-Datensätze zum beliebigen Löschen.",
|
|
"Der Satzzettel und das Tagesprotokoll gehören in dasselbe Modell, nicht in getrennte Tools.",
|
|
"Berechnungen und Materialflüsse bleiben nachvollziehbar, auch wenn die Oberfläche schlank bleibt.",
|
|
],
|
|
outcome: "Ein betreibbares System mit öffentlicher Demo. Vertrauliche Kundendaten, Mengen und interne Kennzahlen bleiben bewusst außen vor.",
|
|
proof: "Öffentliche Produkt-Demo unter batch.w-make.com. Die Case Study beschreibt das Modell, nicht den Kundenstand.",
|
|
lesson: "Ein Produktionssystem wird besser, wenn die fachliche Irreversibilität Teil der Architektur ist — nicht eine Validierung im Formular.",
|
|
stages: [
|
|
{ title: "Rezept", text: "Zusammensetzung und Version bleiben mit dem späteren Verbrauch verbunden." },
|
|
{ title: "Satz", text: "Der Satzzettel ist ein fachlicher Auftrag, kein Ausdruck einer Tabelle." },
|
|
{ title: "Protokoll", text: "Der Tag schließt als nachvollziehbarer Zustand, nicht als exportierte Liste." },
|
|
],
|
|
},
|
|
en: {
|
|
status: "Public product demo",
|
|
label: "Industrial software · product development",
|
|
title: "W-Make Batch",
|
|
summary: "Batch management for container-glass production — from the batch ticket to the daily log.",
|
|
stance: "The production product. A public demo shows the flow, not a client installation.",
|
|
stack: "Node.js · Express · SQLite · Web Components · native tests · Playwright",
|
|
role: "Domain model, full-stack development, and technical quality.",
|
|
situation: "A production process lives on recipes, material use, silos, shifts, and calculations. Built as a loose set of screens, the domain relationships disappear — and bookings become editable table rows.",
|
|
decisions: [
|
|
"Domain bookings are states, not CRUD records that can be deleted at will.",
|
|
"The batch ticket and the daily log belong in one model, not in separate tools.",
|
|
"Calculations and material flows stay traceable even when the interface stays lean.",
|
|
],
|
|
outcome: "An operable system with a public demo. Confidential client data, volumes, and internal metrics stay unpublished.",
|
|
proof: "Public product demo at batch.w-make.com. This case study describes the model, not a client installation.",
|
|
lesson: "A production system improves when domain irreversibility is part of the architecture — not a form validation.",
|
|
stages: [
|
|
{ title: "Recipe", text: "Composition and version stay tied to later consumption." },
|
|
{ title: "Batch", text: "The ticket is a domain order, not a printout of a table." },
|
|
{ title: "Log", text: "The day closes as a traceable state, not an exported list." },
|
|
],
|
|
},
|
|
},
|
|
"batchmaker-studio": {
|
|
number: "02",
|
|
images: [
|
|
{ src: "/portfolio/batchmaker-recipes.jpg", de: "Rezeptarbeitsplatz von Batchmaker Studio.", en: "Batchmaker Studio recipe workspace." },
|
|
{ src: "/portfolio/batchmaker-validation.jpg", de: "Validierung als eigener Schritt.", en: "Validation as a dedicated step." },
|
|
{ src: "/portfolio/batchmaker-settings.jpg", de: "Konfiguration für Redox, Kalibrierung und Oxide.", en: "Configuration for redox, calibration, and oxides." },
|
|
],
|
|
de: {
|
|
status: "Eigenständig betreibbares Produkt",
|
|
label: "Standalone · Recipe Engineering",
|
|
title: "Batchmaker Studio",
|
|
summary: "Schlankes Recipe-Studio für Rezepte, Rohstoffe, Satzzettel und Glaschemie.",
|
|
stance: "Die Werkbank am Schreibtisch. Derselbe Fachkern, bewusst ohne Silos, Schichten und laufende Buchungen.",
|
|
stack: "Node.js · Express · node:sqlite · Vanilla JS · Vite · native Tests",
|
|
role: "Produktarchitektur, Full-Stack-Entwicklung und Calculation Engine.",
|
|
situation: "Rezeptarbeit braucht denselben fachlichen Kern wie die Produktion — aber nicht Silos, Schichten und laufende Buchungen. Ein volles Betriebssystem ist für die Entwicklung am Schreibtisch zu schwer.",
|
|
decisions: [
|
|
"Derselbe Domänenkern, bewusst kleinerer Zuschnitt.",
|
|
"Eigener Server, eigene SQLite-Datei, eigene schlanke Oberfläche.",
|
|
"Validierung als sichtbarer Schritt, nicht als versteckte Nebenwirkung.",
|
|
],
|
|
outcome: "Ein eigenständig betreibbares Werkzeug für Rezeptentwicklung, Glaschemie und druckfertige Satzzettel. Keine unbelegten Effizienzversprechen.",
|
|
proof: "Öffentliche Case Study und der bewusst getrennte Produktzuschnitt gegenüber W-Make Batch.",
|
|
lesson: "Produktgrenzen sind Architektur. Ein gutes Werkzeug muss den Nachbarprozess nicht vollständig abbilden.",
|
|
stages: [
|
|
{ title: "Rezepte", text: "Versionieren, vergleichen, importieren und exportieren." },
|
|
{ title: "Glaschemie", text: "Oxide, Redox, Physik und nachvollziehbare Berechnungen." },
|
|
{ title: "Satzzettel", text: "Entwurf, Skalierung, Vorschau, druckfertige Ausgabe." },
|
|
],
|
|
},
|
|
en: {
|
|
status: "Independently operable product",
|
|
label: "Standalone · recipe engineering",
|
|
title: "Batchmaker Studio",
|
|
summary: "A lean recipe studio for recipes, raw materials, batch tickets, and glass chemistry.",
|
|
stance: "The desk-side workbench. The same domain core, deliberately without silos, shifts, and live bookings.",
|
|
stack: "Node.js · Express · node:sqlite · Vanilla JS · Vite · native tests",
|
|
role: "Product architecture, full-stack development, and calculation engine.",
|
|
situation: "Recipe work needs the same domain core as production — but not silos, shifts, and live bookings. A full operations system is too heavy for desk-side development.",
|
|
decisions: [
|
|
"The same domain core, deliberately smaller scope.",
|
|
"Its own server, SQLite file, and lean browser interface.",
|
|
"Validation as a visible step, not a hidden side effect.",
|
|
],
|
|
outcome: "An independently operable tool for recipe work, glass chemistry, and print-ready batch tickets. No unverifiable efficiency claims.",
|
|
proof: "This public case study and the deliberate product boundary against W-Make Batch.",
|
|
lesson: "Product boundaries are architecture. A good tool does not have to model the neighbouring process in full.",
|
|
stages: [
|
|
{ title: "Recipes", text: "Version, compare, import, and export." },
|
|
{ title: "Chemistry", text: "Oxides, redox, physics, and traceable calculations." },
|
|
{ title: "Tickets", text: "Draft, scale, preview, print-ready output." },
|
|
],
|
|
},
|
|
},
|
|
};
|
|
|
|
export const projectSlugs = Object.keys(projects) as ProjectSlug[];
|
|
|
|
export function getProject(slug: string, locale: Locale): LocalizedProject | null {
|
|
if (!(slug in projects)) return null;
|
|
const entry = projects[slug as ProjectSlug];
|
|
return {
|
|
slug: slug as ProjectSlug,
|
|
number: entry.number,
|
|
href: entry.href,
|
|
images: entry.images,
|
|
...entry[locale],
|
|
};
|
|
}
|
|
|
|
export function getProjects(locale: Locale): LocalizedProject[] {
|
|
return projectSlugs.map((slug) => getProject(slug, locale)!);
|
|
}
|