COVERCANVAS LIVE ADAPTER AND FINALISATION MAP ============================================================================ FILE: /home/shire3d/ARMOR/agents/apps/covercanvas/src/lib/geometryService.js ---------------------------------------------------------------------------- 00001: // GeometryService adapter — development mock. 00002: // Every mocked response includes simulation: true. 00003: // Never display "Print Ready", "Export Complete" or "Validated Geometry" from a simulated response. 00004: 00005: // Assemble the full LimbForge Geometry Engine request payload from project data. 00006: // Includes original + processed artwork, processing settings, layer transforms, 00007: // projection/application mode, emboss/engraving/relief/stencil settings, cover 00008: // model reference and all zone data. 00009: export function buildGeometryRequest({ project, cover, layers = [], assets = [], concept = null } = {}) { 00010: const activeLayers = (layers || []).filter(l => l.visible); 00011: const assetFor = (l) => (assets || []).find(a => a.id === l?.artworkAssetId); 00012: const appMode = project?.applicationMode || 'colour_texture'; 00013: const gs = project?.geometrySettings || {}; 00014: return { 00015: project_id: project?.id || null, 00016: simulation: true, 00017: cover_model: { 00018: reference: cover?.sourceFile || null, 00019: id: cover?.id || null, 00020: name: cover?.name || null, 00021: limb_type: cover?.limbType || null, 00022: side: cover?.side || null, 00023: source_format: cover?.sourceFormat || null, ... 00079: } 00080: 00081: const simulated = (operation, extra = {}) => 00082: new Promise(resolve => setTimeout(() => resolve({ 00083: ok: true, 00084: simulation: true, 00085: operation, 00086: service_version: 'mock-0.1', 00087: ...extra 00088: }), 600)); 00089: 00090: export const GeometryService = { 00091: buildGeometryRequest, 00092: checkHealth: () => simulated('health', { status: 'simulation_only', connected: false }), 00093: uploadCover: payload => simulated('upload_cover', { cover_reference: payload?.cover_model?.reference || payload?.sourceFile || null }), 00094: uploadArtwork: payload => simulated('upload_artwork', { artwork_reference: payload?.artwork?.original_reference || payload?.originalFile || null }), 00095: generatePromptConcepts: payload => simulated('prompt_concepts', { concepts: [], prompt: payload?.promptText || '' }), 00096: createProjectionPreview: payload => simulated('projection_preview', { projection: payload?.application_mode || payload?.projectionMode || 'planar', layers: payload?.layers?.length || 0, real: false }), 00097: generateEmboss: payload => simulated('emboss', { settings: payload?.emboss, real: false }), 00098: generateEngraving: payload => simulated('engraving', { settings: payload?.engraving, real: false }), 00099: generateRelief: payload => simulated('relief', { settings: payload?.relief, real: false }), 00100: generateStencil: payload => simulated('stencil', { settings: payload?.stencil, real: false }), 00101: validateGeometry: payload => simulated('validate_geometry', { request: !!payload, real: false }), 00102: exportCover: () => Promise.resolve({ 00103: ok: false, 00104: simulation: true, 00105: blocked: true, 00106: error: 'Real geometry service and writable SHiREVault are required for export.' 00107: }), 00108: getJobStatus: id => simulated('job_status', { id, status: 'completed', progress: 100, real: false }), 00109: 00110: // --- Design-first (Universal Design → Cover Variant) adapter methods --- 00111: // All mocked with simulation: true. None of these unlock Real Geometry, 00112: // Validated, or Print Ready states. 00113: 00114: // Evaluate browser-available cover metadata for design compatibility. 00115: analyseCoverCompatibility: payload => simulated('analyse_cover_compatibility', { 00116: cover_id: payload?.coverModelId || payload?.cover?.id || null, 00117: design_id: payload?.universalDesignId || payload?.design?.id || null, 00118: level: 'requires_processing', ... 00153: validateCoverVariant: payload => simulated('validate_cover_variant', { 00154: variant_id: payload?.variantId || null, 00155: real: false 00156: }), 00157: 00158: // Export remains blocked until real geometry service + writable SHiREVault exist. 00159: exportCoverVariant: () => Promise.resolve({ 00160: ok: false, 00161: simulation: true, 00162: blocked: true, 00163: error: 'Real geometry service and writable SHiREVault are required to export a cover variant.' 00164: }) 00165: }; FILE: /home/shire3d/ARMOR/agents/apps/covercanvas/src/lib/storageAdapter.js ---------------------------------------------------------------------------- 00001: // SHiREVault storage adapter. 00002: // Final LimbForge projects must use the SHiREVault network drive at /SHiRELimbs. 00003: // During the Base44 MVP, Base44-hosted files are temporary prototype storage only. 00004: 00005: const verify = settings => ({ 00006: mounted: settings?.shireVaultStatus === 'connected', 00007: writable: settings?.shireVaultWritable === true, 00008: pathValid: settings?.shireVaultPath === '/SHiRELimbs' 00009: }); 00010: 00011: export const storageAdapter = { 00012: prototypeLabel: 'Temporary Base44 prototype storage — not SHiREVault', 00013: requiredRoot: '/SHiRELimbs', 00014: verifyFinalStorage: verify, 00015: canFinalize: settings => { 00016: const v = verify(settings); 00017: return v.mounted && v.writable && v.pathValid; 00018: } 00019: }; FILE: /home/shire3d/ARMOR/agents/apps/covercanvas/src/pages/Settings.jsx ---------------------------------------------------------------------------- 00005: import PageHeader from '@/components/app/PageHeader'; 00006: import StatusBadge from '@/components/app/StatusBadge'; 00007: import { storageAdapter } from '@/lib/storageAdapter'; 00008: 00009: const defaults = { 00010: geometryServiceUrl: 'https://geometry.shire.example/v1', geometryServiceAuth: 'none', healthCheckEndpoint: '/health', 00011: maxUploadSize: 50, defaultEmbossHeight: 1.2, defaultEngravingDepth: 0.8, minimumFeatureSize: 0.6, minimumWallThickness: 2, 00012: geometryServiceStatus: 'simulation_only', shireVaultStatus: 'not_connected', shireVaultPath: '/SHiRELimbs', shireVaultWritable: false, 00013: aiConceptServiceUrl: 'built-in InvokeLLM', defaultStorageType: 'prototype', allowedFileFormats: ['stl', 'obj', 'glb', 'png', 'jpg', 'jpeg', 'webp', 'svg'] 00014: }; 00015: 00016: export default function Settings() { 00017: const [s, setS] = useState(defaults); 00018: const [id, setId] = useState(null); 00019: const [saved, setSaved] = useState(false); 00020: const { mode, setMode } = useExperienceMode(); 00021: const [user, setUser] = useState(null); 00022: 00023: useEffect(() => { ... 00040: }; 00041: const v = storageAdapter.verifyFinalStorage(s); 00042: 00043: return ( 00044: <> 00045: 00046:
00047:

Experience

00048:
00049:
00069:
00070:
00071:
00072:

Geometry service

00073: 00074: 00075: 00076:
Service status
00077:
00078:
00079:

SHiREVault final storage

00080: 00081:
Network mount
00082: 00083: {(!v.mounted || !v.writable || !v.pathValid) &&

Final processing and export are blocked. Temporary Base44 files are prototype storage only; there is no local final-output fallback.

} 00084:
00085:
00086:

AI concept service

00087: 00088:

PromptForge concept text uses the built-in InvokeLLM integration; preview images use GenerateImage (integration credits).

00089:
00090:
00091:

Storage & formats

00092: 00093: 00094:
00095:
00096:

Manufacturing defaults

00097:
00098: {[['maxUploadSize', 'Maximum upload (MB)'], ['defaultEmbossHeight', 'Emboss height (mm)'], ['defaultEngravingDepth', 'Engraving depth (mm)'], ['minimumFeatureSize', 'Minimum feature (mm)'], ['minimumWallThickness', 'Minimum wall (mm)']].map(([k, label]) => ( 00099: FILE: /home/shire3d/ARMOR/agents/apps/covercanvas/src/pages/Jobs.jsx ---------------------------------------------------------------------------- 00002: import { Play, Cpu, AlertCircle } from 'lucide-react'; 00003: import { base44 } from '@/api/base44Client'; 00004: import PageHeader from '@/components/app/PageHeader'; 00005: import StatusBadge from '@/components/app/StatusBadge'; 00006: import { useExperienceMode } from '@/lib/ExperienceMode'; 00007: import { GeometryService, buildGeometryRequest } from '@/lib/geometryService'; 00008: import { storageAdapter } from '@/lib/storageAdapter'; 00009: 00010: export default function Jobs() { 00011: const { mode } = useExperienceMode(); 00012: const guided = mode === 'guided'; 00013: const [jobs, setJobs] = useState([]); 00014: const [projects, setProjects] = useState([]); 00015: const [selected, setSelected] = useState(''); 00016: const [busy, setBusy] = useState(false); 00017: const [settings, setSettings] = useState(null); ... 00031: useEffect(() => { load(); }, []); 00032: 00033: const submit = async () => { 00034: if (!selected) return; 00035: setBusy(true); 00036: let request = { simulation: true, project_id: selected }; 00037: try { 00038: const project = await base44.entities.CoverCanvasProject.get(selected); 00039: const [layers, assets] = await Promise.all([ 00040: base44.entities.ArtworkLayer.filter({ projectId: selected }).catch(() => []), 00041: base44.entities.ArtworkAsset.filter({ projectId: selected }).catch(() => []) 00042: ]); 00043: let cover = null; 00044: if (project?.coverModelId) { try { cover = await base44.entities.CoverModel.get(project.coverModelId); } catch { cover = null; } } 00045: request = buildGeometryRequest({ project, cover, layers: Array.isArray(layers) ? layers : [], assets: Array.isArray(assets) ? assets : [] }); 00046: } catch { /* fall back to minimal request */ } 00047: try { 00048: const job = await base44.entities.GeometryJob.create({ projectId: selected, jobType: 'projection_preview', status: 'processing', progress: 20, requestPayload: request }); 00049: await GeometryService.createProjectionPreview(request); 00050: await base44.entities.GeometryJob.update(job.id, { status: 'completed', progress: 100, completedDate: new Date().toISOString(), resultPayload: { simulation: true, request } }); 00051: } catch { /* simulation best-effort */ } 00052: setBusy(false); 00053: load(); 00054: }; 00055: const canFinal = storageAdapter.canFinalize(settings); 00056: const safeJobs = Array.isArray(jobs) ? jobs : []; 00057: const safeProjects = Array.isArray(projects) ? projects : []; 00058: 00059: return ( 00060: <> 00061: 00062: 00063:
00064: 00065: 00066: 00067:
00068:
{guided ? 'Final processing is unavailable until the live geometry engine and SHiRE storage are connected.' : 'Final processing is blocked until the deployment adapter verifies a live geometry service and writable SHiREVault at /SHiRELimbs.'}
00069: 00070:
00071:

{guided ? 'Your processing jobs' : 'Job queue'}

00072: {safeJobs.map(j => { 00073: const active = j.status === 'processing'; 00074: const name = safeProjects.find(p => p.id === j.projectId)?.name || j.projectId; 00075: return ( ... 00079:

{j.id.slice(0, 12)}

00080:
00081: {!guided && ( 00082:
00083:

Operation: {j.jobType?.replaceAll('_', ' ')}

00084:

Engine: {j.simulation ? 'simulation' : 'live'}

00085:
00086: )} 00087:
00088:
00089:
00090:
00091: {j.progress}% FILE: /home/shire3d/ARMOR/agents/apps/covercanvas/src/pages/Validation.jsx ---------------------------------------------------------------------------- 00003: import { ShieldCheck, Play, LockKeyhole, CheckCircle2, AlertTriangle, XCircle, Clock3, ChevronDown, Activity, Wrench, Eye } from 'lucide-react'; 00004: import { base44 } from '@/api/base44Client'; 00005: import PageHeader from '@/components/app/PageHeader'; 00006: import StatusBadge from '@/components/app/StatusBadge'; 00007: import { useExperienceMode } from '@/lib/ExperienceMode'; 00008: import { GeometryService, buildGeometryRequest } from '@/lib/geometryService'; 00009: import { storageAdapter } from '@/lib/storageAdapter'; 00010: 00011: const names = [ 00012: 'Cover loaded successfully', 'Drawing loaded successfully', 'Drawing resolution adequate', 'Drawing cleaned', 00013: 'Minimum line thickness', 'Minimum feature size', 'Artwork within decoration zone', 00014: 'No overlap with protected zones', 'Minimum wall thickness passed', 'Attachment geometry preserved', 00015: 'Ossiguard area preserved', 'Required SHiRE logo present', 'Geometry service connected', 00016: 'Real geometry generated', 'Manifold mesh passed', 'Final export available' 00017: ]; 00018: const icons = { pass: CheckCircle2, warning: AlertTriangle, fail: XCircle, not_checked: Clock3, simulation_only: Clock3 }; 00019: 00020: const groups = [ 00021: ['Cover integrity', ['Cover loaded successfully']], 00022: ['Artwork quality', ['Drawing loaded successfully', 'Drawing resolution adequate', 'Drawing cleaned', 'Minimum line thickness', 'Minimum feature size', 'Artwork within decoration zone']], 00023: ['Prosthetic clearance', ['No overlap with protected zones', 'Minimum wall thickness passed']], 00024: ['Connector preservation', ['Attachment geometry preserved', 'Ossiguard area preserved']], 00025: ['Manufacturing readiness', ['Geometry service connected', 'Real geometry generated', 'Manifold mesh passed', 'Final export available']], 00026: ['SHiRE compliance', ['Required SHiRE logo present']] 00027: ]; 00028: 00029: const friendlyGroups = [ 00030: { label: 'Design quality', checks: ['Drawing loaded successfully', 'Drawing resolution adequate', 'Drawing cleaned', 'Minimum line thickness', 'Minimum feature size', 'Artwork within decoration zone'] }, 00031: { label: 'Cover safety', checks: ['Cover loaded successfully', 'No overlap with protected zones', 'Minimum wall thickness passed'] }, 00032: { label: 'Branding', checks: ['Required SHiRE logo present'] }, 00033: { label: '3D processing', checks: ['Geometry service connected', 'Real geometry generated', 'Manifold mesh passed', 'Final export available'] } 00034: ]; 00035: 00036: const friendly = { 00037: 'Cover loaded successfully': { what: 'A cover is selected for this design.', why: 'Artwork is placed on a real cover shape.' }, 00038: 'Drawing loaded successfully': { what: 'At least one design piece is added.', why: 'You need a design before we can check it.' }, 00039: 'Drawing resolution adequate': { what: 'The design image is clear enough.', why: 'Small images may print blurry.' }, 00040: 'Drawing cleaned': { what: 'The drawing has been prepared.', why: 'A cleaned drawing prints more reliably.' }, ... 00047: 'Ossiguard area preserved': { what: 'The Ossiguard area is untouched.', why: 'This area must stay clear.' }, 00048: 'Required SHiRE logo present': { what: 'The SHiRE creator mark is present.', why: 'Validated designs show the SHiRE mark.' }, 00049: 'Geometry service connected': { what: '3D processing is connected.', why: 'Real geometry needs the live engine.' }, 00050: 'Real geometry generated': { what: 'Real 3D geometry has been created.', why: 'Only the engine can make printable geometry.' }, 00051: 'Manifold mesh passed': { what: 'The 3D model is closed.', why: 'An open surface cannot be printed.' }, 00052: 'Final export available': { what: 'The final file can be saved to SHiRE storage.', why: 'Export needs real geometry and verified storage.' } 00053: }; 00054: 00055: // Friendly fix action per check (shown for warning/fail where a fix exists). 00056: const fixAction = (name, projectId) => { 00057: const p = projectId ? `?project=${projectId}` : ''; 00058: switch (name) { 00059: case 'Cover loaded successfully': return { label: 'Choose a cover', to: `/new-project` }; 00060: case 'Drawing loaded successfully': return { label: 'Add a design', to: `/studio${p}` }; 00061: case 'Drawing cleaned': return { label: 'Prepare drawing', to: `/sketch-3d${p}` }; 00062: case 'Required SHiRE logo present': return { label: 'Add SHiRE logo', to: `/studio${p}` }; 00063: case 'Geometry service connected': return { label: 'Reconnect 3D processing', to: `/settings` }; 00064: case 'Final export available': return { label: 'Reconnect storage', to: `/settings` }; 00065: default: return { label: 'Fix in Studio', to: `/studio${p}` }; 00066: } 00067: }; 00068: 00069: export default function Validation() { 00070: const projectId = new URLSearchParams(location.search).get('project'); 00071: const navigate = useNavigate(); 00072: const { mode } = useExperienceMode(); 00073: const guided = mode === 'guided'; 00074: const [checks, setChecks] = useState(names.map(name => ({ name, status: 'not_checked' }))); 00075: const [settings, setSettings] = useState(null); 00076: const [busy, setBusy] = useState(false); ... 00095: layers = Array.isArray(l) ? l : []; 00096: assets = Array.isArray(a) ? a : []; 00097: if (project?.coverModelId) { try { cover = await base44.entities.CoverModel.get(project.coverModelId); } catch { cover = null; } } 00098: } catch { /* project data optional for preview */ } 00099: 00100: // Best-effort simulation call; never used to infer a real pass. 00101: try { await GeometryService.validateGeometry(buildGeometryRequest({ project, cover, layers, assets })); } catch { /* offline — fine */ } 00102: 00103: const svcConnected = settings?.geometryServiceStatus === 'connected'; 00104: const vaultOk = storageAdapter.canFinalize(settings); 00105: 00106: // Evidence (only these can be confirmed in the browser). 00107: const hasProject = !!project; 00108: const hasCover = !!(cover && cover.sourceFile); 00109: const visibleLayers = layers.filter(l => l.visible); 00110: const hasArtwork = visibleLayers.length > 0; 00111: const assetsForLayers = visibleLayers.map(l => assets.find(a => a.id === l.artworkAssetId)); ... 00135: case 'Minimum feature size': 00136: case 'Minimum wall thickness passed': 00137: case 'Attachment geometry preserved': 00138: case 'Ossiguard area preserved': 00139: case 'Manifold mesh passed': 00140: return { name, status: 'simulation_only', note: 'Confirmed by the real Geometry Engine during processing.' }; 00141: case 'Real geometry generated': 00142: return { name, status: 'simulation_only', note: 'Browser preview only — real geometry is produced by the external engine.' }; 00143: case 'Final export available': 00144: return { name, status: 'fail', note: 'Final export needs real geometry and verified SHiRE storage.' }; 00145: default: 00146: return { name, status: 'not_checked' }; 00147: } 00148: }); 00149: setChecks(next); 00150: const failCount = next.filter(c => c.status === 'fail').length; 00151: const pending = next.filter(c => c.status === 'simulation_only' || c.status === 'not_checked' || c.status === 'warning').length; 00152: setOverall(failCount > 0 ? 'CANNOT CONTINUE YET' : pending > 0 ? 'NEEDS A SMALL CHANGE' : 'READY FOR REAL 3D PROCESSING'); 00153: try { 00154: await base44.entities.ValidationReport.create({ 00155: projectId, overallStatus: failCount > 0 ? 'fail' : 'simulation_only', checks: next, 00156: warnings: ['Preview checks are not manufacturing validation.'], failures: next.filter(c => c.status === 'fail').map(c => c.name), 00157: generatedDate: new Date().toISOString() 00158: }); 00159: } catch { /* non-fatal */ } 00160: setBusy(false); 00161: }; 00162: 00163: const blocked = checks.some(x => x.status === 'fail' || x.status === 'not_checked' || x.status === 'simulation_only' || x.status === 'warning') || !storageAdapter.canFinalize(settings); 00164: const toggle = (g) => setOpen(s => { const n = new Set(s); n.has(g) ? n.delete(g) : n.add(g); return n; }); 00165: const checkByName = (n) => checks.find(c => c.name === n) || { status: 'not_checked' }; 00166: 00167: const overallTone = overall === 'READY FOR REAL 3D PROCESSING' ? 'ok' : overall === 'NEEDS A SMALL CHANGE' ? 'warn' : overall === 'CANNOT CONTINUE YET' ? 'bad' : 'idle'; 00168: const overallClass = overallTone === 'ok' ? 'text-emerald-300 border-emerald-400/40 bg-emerald-400/10' 00169: : overallTone === 'warn' ? 'text-amber-200 border-amber-400/40 bg-amber-400/10' 00170: : overallTone === 'bad' ? 'text-red-300 border-red-500/40 bg-red-500/10' ... 00174: return ( 00175: <> 00176: {busy ? 'Checking…' : 'Run checks'}} /> 00177:
00178:

{busy ? 'Checking your design…' : overall === 'NOT CHECKED' ? 'Not checked yet' : overall}

00179:

{overall === 'READY FOR REAL 3D PROCESSING' ? 'Everything we can check in the browser looks good. Submit for real 3D processing to finish.' : 'Some checks need attention. Items marked Simulation require the real Geometry Engine.'}

00180:
00181: 00182:
00183: {friendlyGroups.map(group => { 00184: const groupChecks = group.checks.map(checkByName); 00185: const groupFail = groupChecks.some(c => c.status === 'fail'); 00186: return ( ... 00197: const needsFix = c.status === 'fail' || c.status === 'warning'; 00198: const fa = fixAction(c.name, projectId); 00199: return ( 00200:
00201:
00202: 00203:

{f.what}

00204: 00205:
00206: {c.status !== 'pass' &&

{f.why}{c.note ? ` ${c.note}` : ''}

} 00207: {needsFix && ( 00208:
00209: ... 00220:
00221: 00222:
00223:
00224:

Submit for real 3D processing

00225:

Final geometry, printing and export are confirmed by the Geometry Engine and SHiRE storage — not by this preview.

00226:
00227: 00228:
00229: 00230: ); 00231: } 00232: 00233: return ( 00234: <> 00235: {busy ? 'Checking…' : 'Run checks'}} /> 00236:
3D Preview — final printable geometry not yet generated. Real wall, manifold, clearance and export checks require the external geometry engine and SHiREVault.
00237: 00238:
00239: {groups.map(([label, groupNames]) => { 00240: const groupChecks = groupNames.map(checkByName); 00241: const counts = { pass: 0, warning: 0, fail: 0, not_checked: 0, simulation_only: 0 }; 00242: groupChecks.forEach(c => counts[c.status] = (counts[c.status] || 0) + 1); 00243: const isOpen = open.has(label); 00244: return ( 00245:
00246: 00259: {isOpen && ( 00260:
00261: {groupChecks.map(c => { 00262: const Icon = icons[c.status] || Clock3; 00263: return ( 00264:
00265: 00266:

{c.name}

{c.note &&

{c.note}

}
00267: 00268:
00269: ); 00270: })} 00271:
00272: )}