feat: ship production deploy, Playwright e2e, and agent implementer

Add Docker atomic deploy tooling, CI, Playwright smoke/visual suite,
shared industrial theme, physics invariants, quality-mode wiring,
and a worktree-isolated agent implementer MVP with hard safety limits.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Даня Архипов
2026-07-15 17:14:01 +00:00
parent 985f7c327d
commit b5985f33bb
34 changed files with 2076 additions and 71 deletions

View File

@@ -23,6 +23,8 @@ import type { Category } from '../../domain/types';
import { PhysicalPlaybackItem } from './PhysicalPlaybackItem';
import { DEMO_PLAYLIST, PLAYLIST_LENGTH } from '../../domain/demoPlaylist';
import { cumulativePlaylistDurationMs, getPlaylistCaseDurationMs } from '../../domain/continuousPlayback';
import { INDUSTRIAL_PALETTE } from '../../domain/industrialTheme';
import { detectQualityMode, getQualitySettings, type QualityMode } from '../../domain/qualityMode';
import {
getCameraConfig,
smoothCameraTransition,
@@ -60,6 +62,7 @@ export interface SorterDigitalTwinContinuousProps {
onContextLost?: () => void;
autoCameraEnabled?: boolean;
viewportType?: ViewportType;
qualityMode?: QualityMode;
}
/**
@@ -69,24 +72,24 @@ export interface SorterDigitalTwinContinuousProps {
* Accents: subtle, not overly bright
*/
const COLORS = {
background: '#f4f7fb',
floor: '#e8eef6',
gridCell: '#d0dae8',
gridSection: '#b8c8dc',
conveyorFrame: '#8a9bb0', // Industrial metal gray
belt: '#6b8298', // Matte PVC blue-gray
beltStripe: '#7d96ad', // Subtle stripe
sideGuards: '#7a8fa3', // Metal guards
rollers: '#9aa8b8', // Brushed metal
supports: '#a0afc0', // Support legs
motor: '#5a6a7a', // Dark motor housing
sensorAccent: '#3b82f6', // Blue sensor (less saturated)
sensorActive: '#60a5fa', // Active state
gateFrame: '#7a8a9a', // Gate metal
routeB: '#16a34a', // Green (softer)
routeC: '#ea580c', // Orange (softer)
routeD: '#7c3aed', // Purple (softer)
itemShadow: '#3a4a5a', // Contact shadow
background: INDUSTRIAL_PALETTE.background,
floor: INDUSTRIAL_PALETTE.floor,
gridCell: INDUSTRIAL_PALETTE.gridCell,
gridSection: INDUSTRIAL_PALETTE.gridSection,
conveyorFrame: INDUSTRIAL_PALETTE.frame,
belt: INDUSTRIAL_PALETTE.belt,
beltStripe: INDUSTRIAL_PALETTE.beltStripe,
sideGuards: INDUSTRIAL_PALETTE.metal,
rollers: INDUSTRIAL_PALETTE.metal,
supports: INDUSTRIAL_PALETTE.plastic,
motor: INDUSTRIAL_PALETTE.metalDark,
sensorAccent: INDUSTRIAL_PALETTE.sensorAccent,
sensorActive: '#60a5fa',
gateFrame: INDUSTRIAL_PALETTE.metal,
routeB: INDUSTRIAL_PALETTE.routeB,
routeC: INDUSTRIAL_PALETTE.routeC,
routeD: INDUSTRIAL_PALETTE.routeD,
itemShadow: '#3a4a5a',
};
const ITEM_MATERIALS: Record<string, { color: string; roughness: number; metalness?: number }> = {
@@ -1444,15 +1447,19 @@ export default function SorterDigitalTwinContinuous({
onContextLost,
autoCameraEnabled = true,
viewportType = 'desktop',
qualityMode,
}: SorterDigitalTwinContinuousProps) {
const mode = qualityMode ?? detectQualityMode(typeof window !== 'undefined' ? window.innerWidth : 1200);
const quality = getQualitySettings(mode);
const useSimplified = simplified || mode === 'low';
return (
<div className="digital-twin-wrap continuous-twin">
<div className="digital-twin-canvas continuous-canvas">
<Canvas
camera={{ position: [4.5, 3.5, 5.0], fov: 45 }}
dpr={simplified ? [1, 1.25] : [1, 1.5]}
shadows={false}
gl={{ antialias: !simplified, powerPreference: 'high-performance' }}
dpr={[1, quality.dprMax]}
shadows={quality.shadows}
gl={{ antialias: quality.antialias && !useSimplified, powerPreference: 'high-performance' }}
onCreated={({ gl }) => {
const canvas = gl.domElement;
const handleLost = (event: Event) => {
@@ -1466,7 +1473,7 @@ export default function SorterDigitalTwinContinuous({
<Suspense fallback={null}>
<ContinuousScene
playback={playback}
simplified={simplified}
simplified={useSimplified}
autoCameraEnabled={autoCameraEnabled}
viewportType={viewportType}
/>