Skip to content
Closed
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
6,336 changes: 6,268 additions & 68 deletions .faim/derived/sources.pl

Large diffs are not rendered by default.

1,117 changes: 1,034 additions & 83 deletions .faim/derived/structure.pl

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,6 @@ backups/
package-lock.json
.zed/settings.json
.faim/faim.log
# SCIP dependency index (regenerated via scip-typescript; see .faim scanner docs)
index.scip
scip-deps*.jsonl
6 changes: 0 additions & 6 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,6 @@
<link rel="icon" type="image/png" sizes="16x16" href="%BASE_URL%favicon-16x16.png" />
<link rel="apple-touch-icon" sizes="180x180" href="%BASE_URL%apple-touch-icon.png" />
<link rel="manifest" href="%BASE_URL%site.webmanifest" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&icon_names=person_heart"
rel="stylesheet"
/>
<title>Team Lunch</title>
<script>
// Apply the saved theme before first paint to avoid a flash of the wrong theme.
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"prisma:migrate:dev": "prisma migrate dev",
"prisma:migrate:deploy": "prisma migrate deploy",
"auth:seed": "tsx scripts/auth-seed.ts",
"faim:deps": "node ./scripts/faim-scip-deps.mjs",
"deploy": "sh ./scripts/deploy.sh",
"validate": "pwsh -NoProfile -File ./validate.ps1 all"
},
Expand All @@ -69,6 +70,7 @@
},
"devDependencies": {
"@playwright/test": "^1.58.2",
"@sourcegraph/scip-typescript": "^0.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.2.0",
"@testing-library/user-event": "^14.6.1",
Expand Down
31 changes: 31 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

113 changes: 113 additions & 0 deletions reports/ui-ux-assessment.md

Large diffs are not rendered by default.

32 changes: 32 additions & 0 deletions scripts/faim-scip-deps.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/usr/bin/env node
// Refresh the FAIM dependency graph from a fresh scip-typescript index.
// Run when `faim stale` flags scanner_scan facts (912 machine-facts can't be hand-edited).
// ponytail: filter is a single substring exclude; broaden the EXCLUDE list if more generated trees appear.
import { execFileSync } from 'node:child_process';
import { writeFileSync } from 'node:fs';

const EXCLUDE = ['src/server/generated/']; // Prisma client output: regenerated, not authored -> pure noise
const INDEX = 'index.scip';
const FILTERED = 'scip-deps.filtered.jsonl';
const QUERY = 'import graph (scip-typescript)';
// shell: true so Windows resolves the `faim`/`pnpm` .cmd launchers (not directly spawnable).
const run = (cmd, args) => execFileSync(cmd, args, { stdio: ['ignore', 'pipe', 'inherit'], shell: true }).toString();

console.error('> scip-typescript index');
execFileSync('pnpm', ['exec', 'scip-typescript', 'index', '--no-progress-bar'], { stdio: 'inherit', shell: true });

console.error('> faim scan scip');
const lines = run('faim', ['scan', 'scip', INDEX]).split(/\r?\n/).filter(Boolean);
const kept = lines.filter((l) => !EXCLUDE.some((p) => l.includes(p)));
writeFileSync(FILTERED, kept.join('\n') + '\n');
console.error(` kept ${kept.length}, dropped ${lines.length - kept.length}`);

console.error('> faim import --replace');
execFileSync(
'faim',
// shell:true joins args unquoted, so wrap the spaced/paren'd query; the shell strips the quotes,
// faim receives QUERY verbatim -> matches the stored query so --replace stays idempotent.
['import', FILTERED, '--format', 'jsonl', '--source', 'scanner_scan', '--query', `"${QUERY}"`, '--replace'],
{ stdio: 'inherit', shell: true },
);
console.error('done. run `faim validate` to confirm.');
12 changes: 6 additions & 6 deletions src/client/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,30 +135,30 @@ export default function App() {
) {
return {
actionLabel: 'Awaiting lunch delivery...',
phaseLabel: '3/3',
phaseLabel: 'Step 3/3 · Delivery',
countdownTo: activeFoodSelection.deliveryDueAt,
};
}

return {
actionLabel: 'Food selection in progress...',
phaseLabel: '2/3',
phaseLabel: 'Step 2/3 · Ordering',
countdownTo: activeFoodSelection.endsAt,
};
}

if (activePoll) {
return {
actionLabel: 'Cuisine poll in progress...',
phaseLabel: '1/3',
phaseLabel: 'Step 1/3 · Poll',
countdownTo: activePoll.endsAt,
};
}

if (isPollFinishedTransition) {
return {
actionLabel: 'Cuisine poll in progress...',
phaseLabel: '1/3',
phaseLabel: 'Step 1/3 · Poll',
countdownTo: null,
};
}
Expand Down Expand Up @@ -257,14 +257,14 @@ export default function App() {
<img
src={cuisineAroundTheWorldImage}
alt=""
className="h-auto w-auto max-h-full max-w-full object-contain opacity-20"
className="h-auto w-auto max-h-full max-w-full object-contain opacity-[0.08]"
/>
</div>
<div className="absolute inset-0 z-10 flex items-center justify-center">
<img
src={exampleCompanyLogoImage}
alt=""
className="h-auto w-auto max-h-[24%] max-w-[45%] object-contain opacity-20"
className="h-auto w-auto max-h-[24%] max-w-[45%] object-contain opacity-[0.08]"
/>
</div>
</div>
Expand Down
41 changes: 28 additions & 13 deletions src/client/components/FoodDeliveryView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import {
import { useAppDispatch, useAppState } from '../context/AppContext.js';
import { useCountdown, useElapsedSince, formatTime } from '../hooks/useCountdown.js';
import TimerActionHeader from './TimerActionHeader.js';
import { Button } from './ui/Button.js';
import { Input } from './ui/Input.js';
import { useConfirmDialog } from './ui/ConfirmDialog.js';
import {
buildOrderLookupMaps,
buildOrderSummary,
Expand Down Expand Up @@ -161,14 +164,13 @@ function DeliveryEtaControls({
<>
<div className="max-h-48 overflow-y-auto border-b border-border py-1">
{etaOptions.map((minutes) => (
<button key={minutes} type="button" onClick={() => onSaveEta(minutes)} disabled={isSavingEta} className="block w-full px-3 py-1.5 text-left text-sm text-fg hover:bg-surface-muted disabled:opacity-60">
<Button key={minutes} variant="ghost" onClick={() => onSaveEta(minutes)} disabled={isSavingEta} className="w-full rounded-none px-3 py-1.5 text-left text-fg">
{minutes} min
</button>
</Button>
))}
</div>
<div className="p-2">
<input
type="text"
<Input
value={manualEtaMinutes}
onChange={(event) => onManualEtaMinutesChange(event.target.value)}
onKeyDown={(event) => {
Expand All @@ -178,7 +180,7 @@ function DeliveryEtaControls({
}
}}
placeholder="Manual minutes remaining"
className="w-full rounded border border-border px-2 py-1.5 text-sm focus:border-accent focus:outline-none"
className="px-2 py-1.5"
aria-label="Manual minutes remaining"
/>
</div>
Expand Down Expand Up @@ -218,13 +220,13 @@ function DeliveryTimerMenu({

return (
<>
<button type="button" onClick={() => void onConfirmArrival().then((done) => done && closeMenu())} disabled={isConfirmingArrival} className="block w-full border-b border-border bg-success-soft px-3 py-2 text-left text-sm font-medium text-success-fg hover:bg-success-soft disabled:opacity-60">
<Button variant="success" onClick={() => void onConfirmArrival().then((done) => done && closeMenu())} disabled={isConfirmingArrival} className="w-full rounded-none border-x-0 border-t-0 px-3 text-left">
Confirm lunch arrived
</button>
</Button>
{canManageFoodSelection ? (
<button type="button" onClick={() => void onAbortProcess().then((done) => done && closeMenu())} className="block w-full border-b border-border bg-danger-soft px-3 py-2 text-left text-sm font-medium text-danger-fg hover:bg-danger-soft disabled:opacity-60">
<Button variant="danger" onClick={() => void onAbortProcess().then((done) => done && closeMenu())} className="w-full rounded-none border-x-0 border-t-0 px-3 text-left">
Abort process
</button>
</Button>
) : null}
<DeliveryEtaControls
etaOptions={etaOptions}
Expand Down Expand Up @@ -361,9 +363,9 @@ function DeliveryOrdersList({
<span className="text-sm font-semibold text-fg">Total: {formatPrice(totalPrice)}</span>
</div>
<div className="mt-3">
<button type="button" onClick={onCopyOrders} className="w-full rounded border border-accent bg-accent-soft px-3 py-2 text-sm font-medium text-accent-fg hover:bg-accent-soft">
<Button variant="secondary" onClick={onCopyOrders} className="w-full border-accent bg-accent-soft px-3 text-accent-fg hover:bg-accent-soft">
Copy order list
</button>
</Button>
<OrderCopyStatus status={copyStatus} />
</div>
</div>
Expand All @@ -379,6 +381,7 @@ export default function FoodDeliveryView() {
const [copyStatus, setCopyStatus] = useState<'idle' | 'success' | 'error'>('idle');
const [updatingDeliveredIds, setUpdatingDeliveredIds] = useState<Set<string>>(new Set());
const [error, setError] = useState<string | null>(null);
const { confirm, dialog } = useConfirmDialog();
const canManageFoodSelection = isAdminAuthenticatedUser();
const actorLabel = getAuthenticatedDisplayLabel();

Expand Down Expand Up @@ -450,7 +453,11 @@ export default function FoodDeliveryView() {
}

async function onConfirmArrival(): Promise<boolean> {
const confirmed = window.confirm('Confirm lunch has arrived? This cannot be changed afterwards.');
const confirmed = await confirm({
title: 'Confirm lunch has arrived?',
consequenceText: 'This cannot be changed afterwards.',
confirmLabel: 'Confirm arrival',
});
if (!confirmed) return false;

setIsConfirmingArrival(true);
Expand All @@ -467,7 +474,12 @@ export default function FoodDeliveryView() {
}

async function onAbortProcess(): Promise<boolean> {
const confirmed = window.confirm('Abort food selection?');
const confirmed = await confirm({
title: 'Abort food selection?',
consequenceText: 'This stops delivery tracking for this lunch.',
confirmLabel: 'Abort process',
destructive: true,
});
if (!confirmed) return false;

setError(null);
Expand Down Expand Up @@ -517,6 +529,7 @@ export default function FoodDeliveryView() {
}

return (
<>
<div className="mx-auto w-full max-w-3xl p-4">
<DeliveryTimerActions
title={isDue ? 'Lunch should have arrived' : 'Awaiting lunch delivery'}
Expand Down Expand Up @@ -584,5 +597,7 @@ export default function FoodDeliveryView() {
{error && <p className="mt-3 text-sm text-danger-fg">{error}</p>}
</div>
</div>
{dialog}
</>
);
}
25 changes: 13 additions & 12 deletions src/client/components/FoodSelectionAbortControl.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useState } from 'react';
import { Button } from './ui/Button.js';

interface FoodSelectionAbortControlProps {
disabled: boolean;
Expand All @@ -15,32 +16,32 @@ export default function FoodSelectionAbortControl({
return (
<div className="inline-flex items-center gap-2 rounded border border-danger bg-danger-soft px-3 py-2">
<span className="text-sm text-danger-fg">Abort food selection?</span>
<button
type="button"
<Button
variant="danger"
onClick={() => void onAbort().finally(() => setShowAbortConfirm(false))}
disabled={disabled}
className="rounded bg-danger-solid px-3 py-1 text-sm font-medium text-danger-on transition-colors hover:opacity-90 disabled:opacity-50"
className="border-0 bg-danger-solid px-3 py-1 text-danger-on hover:opacity-90"
>
Yes, abort
</button>
<button
type="button"
</Button>
<Button
variant="secondary"
onClick={() => setShowAbortConfirm(false)}
className="rounded border border-border px-3 py-1 text-sm text-fg-muted hover:bg-surface-muted"
className="px-3 py-1 text-fg-muted"
>
Cancel
</button>
</Button>
</div>
);
}

return (
<button
type="button"
<Button
variant="ghost"
onClick={() => setShowAbortConfirm(true)}
className="text-sm text-danger-fg transition-opacity hover:opacity-80"
className="text-danger-fg hover:opacity-80"
>
Abort
</button>
</Button>
);
}
Loading
Loading