Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions app/handoff/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { getHandoffWorkspace } from '@/lib/server/demo-data';
import { getServerTranslator } from '@/lib/i18n/server';
import { SubmitButton } from '@/components/submit-button';
import Link from 'next/link';
import { CheckIcon, HandoffIcon, PrinterIcon } from '@/components/ui-icons';
import { CheckIcon, HandoffIcon, OrdersIcon, PrinterIcon, ProductionIcon } from '@/components/ui-icons';

const shiftOptions = ['night', 'morning', 'afternoon'] as const;
const stageOptions = ['prepared', 'shaped', 'baked', 'ready'] as const;
Expand Down Expand Up @@ -45,10 +45,20 @@ export default async function HandoffPage({
<h1>{t('handoff.title')}</h1>
<p>{t('handoff.description')}</p>
</div>
<Link href="/handoff/print" className="button-secondary" target="_blank" rel="noreferrer">
<PrinterIcon className="button-icon" />
<span>{t('printing.actions.handoffSlip')}</span>
</Link>
<div className="action-cluster">
<Link href="/orders/new" className="button-primary">
<OrdersIcon className="button-icon" />
<span>Create order</span>
</Link>
<Link href="/production" className="button-secondary">
<ProductionIcon className="button-icon" />
<span>Production board</span>
</Link>
<Link href="/handoff/print" className="button-secondary" target="_blank" rel="noreferrer">
<PrinterIcon className="button-icon" />
<span>{t('printing.actions.handoffSlip')}</span>
</Link>
</div>
</section>

{params?.saved ? <p className="inline-success"><CheckIcon className="button-icon" />{t('handoff.savedUpdate', { item: params.saved })}</p> : null}
Expand Down Expand Up @@ -102,6 +112,7 @@ export default async function HandoffPage({
<span className="summary-pill">{focusWip.length} {t('common.entries')}</span>
</div>
<ul className="stack-list muted-list">
{focusWip.length === 0 ? <li><strong>No WIP recorded yet</strong><span>Use the WIP form below when prep, baked items, or packed items need to cross a shift.</span></li> : null}
{focusWip.map((entry) => (
<li key={entry.id}>
<strong>{entry.referenceLabel} · {entry.quantity} {entry.unit}</strong>
Expand All @@ -122,6 +133,7 @@ export default async function HandoffPage({
<span className="summary-pill">{view.packingWatch.length}</span>
</div>
<ul className="stack-list muted-list">
{view.packingWatch.length === 0 ? <li><strong>No packing watch items</strong><span>Orders appear here when delivery or app-delivery work still has remaining quantity.</span></li> : null}
{view.packingWatch.map((order) => {
const providerLabel = getProviderLabel(order, t);
return (
Expand Down Expand Up @@ -152,6 +164,7 @@ export default async function HandoffPage({
<span className="summary-pill">{view.assignmentWatch.length}</span>
</div>
<ul className="stack-list muted-list">
{view.assignmentWatch.length === 0 ? <li><strong>No assignment watch items</strong><span>Own-delivery orders without an assignee appear here.</span></li> : null}
{view.assignmentWatch.map((order) => (
<li key={order.id}>
<strong>{order.customerLabel}</strong>
Expand All @@ -178,6 +191,7 @@ export default async function HandoffPage({
<span className="summary-pill">{view.pickupWatch.length}</span>
</div>
<ul className="stack-list muted-list">
{view.pickupWatch.length === 0 ? <li><strong>No pickup-ready items</strong><span>Pickup orders appear here after their quantities are completed.</span></li> : null}
{view.pickupWatch.map((order) => (
<li key={order.id}>
<strong>{order.customerLabel}</strong>
Expand All @@ -201,6 +215,7 @@ export default async function HandoffPage({
<span className="summary-pill">{view.focusOrders.length} {t('common.orders')}</span>
</div>
<ul className="stack-list muted-list">
{view.focusOrders.length === 0 ? <li><strong>No focus-day orders</strong><span>Create an order to make handoff fulfillment checks useful.</span></li> : null}
{view.focusOrders.map((order) => (
<li key={order.id}>
<strong>{order.customerLabel}</strong>
Expand Down
17 changes: 17 additions & 0 deletions app/orders/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ export default async function OrdersPage({
<span className="summary-pill">{view.recurringTemplates.length} {t('common.templates')}</span>
</div>
<div className="card-grid recurring-grid">
{view.recurringTemplates.length === 0 ? (
<p className="empty-state">No recurring templates yet. Start with one-time orders; recurring demand can be added after the daily flow is working.</p>
) : null}
{view.recurringTemplates.map((template) => (
<article key={template.id} className="subpanel page-stack">
<div className="order-card-header">
Expand Down Expand Up @@ -187,6 +190,20 @@ export default async function OrdersPage({
</div>
</section>

{view.orderGroups.length === 0 ? (
<section className="page-context-card">
<OrdersIcon className="callout-icon" />
<div>
<strong>No orders yet</strong>
<p className="helper-text no-margin">Create the first real order to make the production board and handoff views useful.</p>
<div className="inline-action-row top-gap-small">
<Link href="/orders/new" className="button-primary compact-button">Create first order</Link>
<Link href="/customers" className="button-secondary compact-button">Confirm customers</Link>
</div>
</div>
</section>
) : null}

{view.orderGroups.map((group) => (
<section key={group.productionDate} className="panel page-stack">
<div className="table-header-row">
Expand Down
24 changes: 24 additions & 0 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { detectInstanceGatewayState, shouldRouteToEntryGateway } from '@/lib/ser
import { getServerTranslator } from '@/lib/i18n/server';
import { readAppData } from '@/lib/server/persistence';
import { skossBootstrapRoutes, skossCoreRoutes, skossinaRoutes, type AppPlaneRoute } from '@/lib/application-planes';
import { getRuntimeModeLabel } from '@/lib/server/runtime-mode';
import { ArrowRightIcon, CustomersIcon, HandoffIcon, OrdersIcon, ProductionIcon, SetupIcon, SparklesIcon, TimelineIcon } from '@/components/ui-icons';

type QuickLink = {
Expand All @@ -27,6 +28,7 @@ export default async function HomePage() {
const summary = buildWorkspaceSummary(data);
const { currentUser, visibleWorkspaces, homeWorkspace } = userContext;
const gatewayState = await detectInstanceGatewayState(data);
const runtimeModeLabel = getRuntimeModeLabel();

if (shouldRouteToEntryGateway(gatewayState, Boolean(currentUser))) {
redirect('/entry');
Expand Down Expand Up @@ -110,6 +112,8 @@ export default async function HomePage() {
<span className="summary-pill">{t(`operatingModes.${summary.preferences.operatingMode}.label`)}</span>
{currentUser ? <span className="summary-pill">{currentUser.displayName} · {t(`roles.${currentUser.role}.label`)}</span> : null}
<span className="summary-pill">{t(`nav.${recommendedWorkspace}`)} · {t('home.recommendedFirst')}</span>
<span className="summary-pill">{runtimeModeLabel}</span>
{gatewayState.demoModeActive ? <span className="summary-pill">Demo data active</span> : null}
</div>
<div className="stats-grid">
<div className="stat-card">
Expand Down Expand Up @@ -145,6 +149,26 @@ export default async function HomePage() {
</div>
</section>

<section className="panel page-stack">
<div className="table-header-row">
<div>
<h2>First deploy workflow</h2>
<p className="helper-text">For a real first test, start with customers and order capture, then use production and handoff as the daily operating loop.</p>
</div>
<span className="summary-pill">MWP path</span>
</div>
<div className="inline-action-row">
<Link href={skossinaRoutes.customers} className="button-secondary compact-button">Confirm customers</Link>
<Link href={skossinaRoutes.orders} className="button-primary compact-button">Create orders</Link>
<Link href={skossinaRoutes.production} className="button-secondary compact-button">Track production</Link>
<Link href={skossinaRoutes.handoff} className="button-secondary compact-button">Record handoff</Link>
<Link href={skossCoreRoutes.adminSetup} className="button-secondary compact-button">Basic setup</Link>
</div>
{gatewayState.demoModeActive ? (
<p className="inline-warning">This workspace is marked as demo data. Do not treat records here as live operational data.</p>
) : null}
</section>

<section className="grid-two">
<article className="panel page-stack">
<div className="table-header-row">
Expand Down
30 changes: 26 additions & 4 deletions app/production/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { SubmitButton } from '@/components/submit-button';
import {
CheckIcon,
HandoffIcon,
OrdersIcon,
PrinterIcon,
ProductionIcon,
} from '@/components/ui-icons';
Expand Down Expand Up @@ -56,10 +57,16 @@ export default async function ProductionPage() {
<h1>{t('production.title')}</h1>
<p>{t('production.description')}</p>
</div>
<Link href="/handoff" className="button-secondary">
<HandoffIcon className="button-icon" />
<span>{t('production.recordWip')}</span>
</Link>
<div className="action-cluster">
<Link href="/orders/new" className="button-primary">
<OrdersIcon className="button-icon" />
<span>Create order</span>
</Link>
<Link href="/handoff" className="button-secondary">
<HandoffIcon className="button-icon" />
<span>{t('production.recordWip')}</span>
</Link>
</div>
</section>

{view.boards.map((board) => {
Expand Down Expand Up @@ -108,6 +115,20 @@ export default async function ProductionPage() {
))}
</div>

{board.boardOrders.length === 0 ? (
<section className="page-context-card">
<ProductionIcon className="callout-icon" />
<div>
<strong>No visible orders on this production board</strong>
<p className="helper-text no-margin">Create an order or make sure existing orders are set to show on the production board.</p>
<div className="inline-action-row top-gap-small">
<Link href="/orders/new" className="button-primary compact-button">Create order</Link>
<Link href="/orders" className="button-secondary compact-button">Review orders</Link>
</div>
</div>
</section>
) : null}

<div
className="stats-grid compact-stats-grid"
id={`${sectionIdBase}-demand`}
Expand Down Expand Up @@ -404,6 +425,7 @@ export default async function ProductionPage() {
</span>
</div>
<div className="line-grid-stack">
{board.boardOrders.length === 0 ? <p className="empty-state">No order lines are ready for production updates yet.</p> : null}
{board.boardOrders.map((order) => {
const progress = getOrderProgress(order);
return (
Expand Down
85 changes: 85 additions & 0 deletions docs/mwp-readiness-audit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# MWP Readiness Audit

This audit captures the minimum real workflow needed before a first deploy/test of SKOSS with a small food business. It is intentionally focused on operational readiness, not feature expansion.

## Current workflow map

1. `/entry` is the instance gateway. It separates new activation, existing instance sign-in, restore, demo launch, and non-production recovery tools.
2. `/bootstrap` handles first-time activation. Required steps are admin account and workspace basics; team, rhythm, starter product, and imports are optional.
3. `/login` opens an existing initialized workspace for active users.
4. `/` is the SKOSSina operator home. It summarizes today, shows role-shaped workspaces, and now exposes the first-deploy workflow path: customers, orders, production, handoff, and basic setup.
5. `/admin` and `/admin/setup` are SKOSS Core/admin surfaces for structural setup, users, imports, basic catalog/procurement memory, preferences, and module framing.
6. `/customers` supports customer memory, customer edit/create, customer-linked order creation, and recent customer order context.
7. `/orders` supports recurring demand review, one-time order creation, order cards by production date, and links into order editing.
8. `/orders/new` captures orders with draft customer/product support and a default visible-on-production-board path.
9. `/production` groups visible order demand, tracks line progress, exposes fulfillment queues, WIP snapshots, and print-friendly production output.
10. `/handoff` records WIP and shift handoff notes, monitors fulfillment watches, and exposes print-friendly handoff output.

## Blockers found

No hard blocker was found in the basic route path: a user can bootstrap, sign in, add customers, create an order, see it on production, update line progress, and record handoff/WIP.

The main blockers are readiness and clarity issues rather than missing infrastructure:

- Empty operational states did not always tell a first-time user what to do next.
- Runtime/demo state was clearer on `/entry` and `/admin/setup` than on the operator home.
- Production and handoff could look empty or broken before the first order exists.
- Product setup is still indirect. Operators can type draft products in orders and create a starter product in bootstrap, but dedicated product confirmation remains thin.
- The app still contains demo-seed-oriented copy and example setup surfaces that could be mistaken for real operational readiness.

## Rough edges

- `/bootstrap` has an optional starter product step, but product/catalog management after launch is still mostly implied through order capture and setup recipe links.
- `/orders` showed an empty recurring-demand grid even when recurring templates did not exist.
- `/production` had good empty handling when there were no boards, but not enough guidance when a board date exists with no visible orders.
- `/handoff` showed several empty lists without explaining what makes items appear.
- `/admin` describes setup with suppliers/materials/recipes before customers/orders, which may overemphasize later procurement foundations for an MWP user.

## Demo/dev/real-data risks

- `SKOSS_RUNTIME_MODE=demo` enables destructive local reset tools and demo launch. This is useful for development but dangerous if confused with a live workspace.
- `demoModeActive` is stored in instance state, but before this pass it was most visible at entry rather than in the operator loop.
- Demo seed data can make the product look more complete than a fresh instance. A real first deploy needs obvious messaging when demo data is active.
- Pilot mode is currently the default for production builds unless `SKOSS_RUNTIME_MODE=production` is set. That should be documented and verified before any hosted live use.

## Must-fix before first deploy

- Confirm production runtime configuration: explicitly set and verify `SKOSS_RUNTIME_MODE` for the target environment.
- Ensure a fresh instance does not ship with demo records unless the deploy is explicitly a demo/pilot training workspace.
- Confirm backup/restore behavior for the chosen persistence adapter and document the manual recovery path.
- Add a product/basic catalog confirmation path or make the draft-product flow intentionally documented as the MWP path.
- Validate login/session behavior on the actual target host with at least owner admin and one operator role.
- Run a real scenario: create customer, create order, update production quantities, record WIP, record handoff, print/review output.

## Nice-to-have after first deploy

- Dedicated basic product management outside procurement/recipe setup.
- Better guided first-run checklist on the operator home after bootstrap.
- More complete product/catalog import or quick-add affordances.
- More precise role onboarding for sales, kitchen, and shift lead users.
- Better correction flows for supplier prices and recipes.
- First lightweight backup export UI.

## Explicit non-goals for first deploy

Do not include these in the first deploy readiness scope:

- procurement workflows
- inventory deduction, stock ledgers, or valuation
- WhatsApp integration
- delivery logistics workflows
- printing system integrations beyond browser-print output already present
- full module marketplace behavior
- complex permissions framework
- deploy scripts or infrastructure automation

## Stabilization applied in this pass

- Added operator-home runtime/demo visibility and a first-deploy workflow card.
- Added empty-state guidance to `/orders` for missing recurring templates and missing first orders.
- Added production entry links and clearer empty guidance for production boards with no visible orders.
- Added handoff entry links and empty-list guidance for WIP, packing, assignment, pickup, and focus-day order watches.

## Recommended next PR

Add a narrow basic product/catalog confirmation path for the MWP. Keep it separate from recipes, costing, procurement, and inventory: just enough to confirm product names, default units, active state, and variants so first deploy users are not forced to rely only on draft product text in order lines.
Loading