@rig-c/review 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +242 -7
- package/docs/DESIGN.md +33 -5
- package/docs/REVIEW_PACKAGE.md +115 -0
- package/package.json +1 -1
- package/src/applied-context.ts +285 -0
- package/src/baseline.ts +175 -0
- package/src/bone-anchors.ts +123 -0
- package/src/bones-check.ts +105 -0
- package/src/build-route.ts +109 -0
- package/src/chest-landmarks.ts +122 -0
- package/src/chest-regions.ts +65 -6
- package/src/cli.ts +19 -8
- package/src/config-patch.ts +213 -0
- package/src/cut-edits.ts +184 -0
- package/src/environment.ts +226 -0
- package/src/feedback-export.ts +25 -1
- package/src/front.ts +11 -0
- package/src/handoff.ts +208 -0
- package/src/idle-tracks.ts +401 -0
- package/src/manifest.ts +15 -6
- package/src/mesh-edit.ts +317 -0
- package/src/mesh-entry.ts +351 -0
- package/src/open.ts +12 -5
- package/src/physics-tuning.ts +236 -0
- package/src/producer-files.ts +11 -0
- package/src/producer.ts +158 -0
- package/src/readings-run.ts +129 -0
- package/src/readings.ts +235 -0
- package/src/rebuild.ts +938 -0
- package/src/reference.ts +4 -3
- package/src/requirements-run.ts +160 -0
- package/src/requirements.ts +230 -0
- package/src/retarget.ts +211 -0
- package/src/retune-route.ts +52 -0
- package/src/rig-fields.ts +295 -0
- package/src/server.ts +59 -5
- package/src/session.ts +12 -0
- package/src/setup-mesh.ts +144 -0
- package/src/weights.ts +74 -0
- package/web/bones-panel.ts +230 -0
- package/web/chest-editor.ts +294 -0
- package/web/chest-regions-tool.ts +10 -4
- package/web/cuts-panel.ts +266 -0
- package/web/idle-panel.ts +280 -0
- package/web/index.html +2 -2
- package/web/inspector-blocks.ts +77 -0
- package/web/main.ts +813 -24
- package/web/mesh-editor.ts +303 -0
- package/web/mesh-panel.ts +273 -0
- package/web/part-selection.ts +13 -5
- package/web/part-thumbnails.ts +76 -0
- package/web/physics-panel.ts +151 -0
- package/web/readings-panel.ts +105 -0
- package/web/rebuild-client.ts +216 -0
- package/web/requirements-panel.ts +238 -0
- package/web/retarget-panel.ts +203 -0
- package/web/rig-fields-panel.ts +269 -0
- package/web/rig-overlays.ts +171 -14
- package/web/setup-pose.ts +30 -0
- package/web/styles.css +52 -5
- package/web/weights-view.ts +159 -0
- package/web/workspace-preferences.ts +17 -12
- package/web/workspace-ui.ts +5 -3
|
@@ -8,11 +8,16 @@ export type WorkspaceTheme='system'|'dark'|'light';
|
|
|
8
8
|
export interface WorkspacePanels {leftWidth:number;rightWidth:number;inspectorHeight:number}
|
|
9
9
|
export const panelBounds=Object.freeze({leftWidth:Object.freeze({min:120,max:720}),rightWidth:Object.freeze({min:180,max:900}),inspectorHeight:Object.freeze({min:160,max:900})});
|
|
10
10
|
export const defaultWorkspacePanels:Readonly<WorkspacePanels>=Object.freeze({leftWidth:170,rightWidth:260,inspectorHeight:280});
|
|
11
|
-
type
|
|
12
|
-
|
|
11
|
+
type Flag='defaultCameraSync'|'defaultIdleRepeat'|'settingsCollapsed';
|
|
12
|
+
type Field='theme'|Flag|keyof WorkspacePanels;
|
|
13
|
+
const fields:readonly Field[]=['theme','defaultCameraSync','defaultIdleRepeat','settingsCollapsed','leftWidth','rightWidth','inspectorHeight'];
|
|
14
|
+
const flags:readonly Flag[]=['defaultCameraSync','defaultIdleRepeat','settingsCollapsed'];
|
|
15
|
+
/** Fields added after the record shipped. A stored record that predates one has neither the value nor its
|
|
16
|
+
* stamp: it loads with the default value at revision 0, exactly as if that field had never been edited. */
|
|
17
|
+
const addedFields:ReadonlySet<Field>=new Set(['settingsCollapsed']);
|
|
13
18
|
interface FieldStamp {revision:number;writer:string}
|
|
14
|
-
export interface WorkspacePreferences {schema:'rig-review-preferences/1';revision:number;theme:WorkspaceTheme;defaultCameraSync:boolean;defaultIdleRepeat:boolean;panels:WorkspacePanels;stamps:Record<Field,FieldStamp>}
|
|
15
|
-
export interface WorkspacePreferencePatch {theme?:WorkspaceTheme;defaultCameraSync?:boolean;defaultIdleRepeat?:boolean;panels?:Partial<WorkspacePanels>}
|
|
19
|
+
export interface WorkspacePreferences {schema:'rig-review-preferences/1';revision:number;theme:WorkspaceTheme;defaultCameraSync:boolean;defaultIdleRepeat:boolean;settingsCollapsed:boolean;panels:WorkspacePanels;stamps:Record<Field,FieldStamp>}
|
|
20
|
+
export interface WorkspacePreferencePatch {theme?:WorkspaceTheme;defaultCameraSync?:boolean;defaultIdleRepeat?:boolean;settingsCollapsed?:boolean;panels?:Partial<WorkspacePanels>}
|
|
16
21
|
export interface PreferenceStorage {getItem(key:string):string|null;setItem(key:string,value:string):void}
|
|
17
22
|
export interface PreferenceStatus {state:'available'|'memory-only'|'corrupt';detail:string}
|
|
18
23
|
export interface PreferenceEventSource {addEventListener(type:string,listener:EventListener):void;removeEventListener(type:string,listener:EventListener):void}
|
|
@@ -23,21 +28,21 @@ const theme=(v:unknown):WorkspaceTheme=>{if(v!=='system'&&v!=='dark'&&v!=='light
|
|
|
23
28
|
const boolean=(v:unknown,label:string):boolean=>{if(typeof v!=='boolean')throw new Error(`${label}: expected boolean`);return v;};
|
|
24
29
|
const panel=(v:unknown,key:keyof WorkspacePanels):number=>{const {min,max}=panelBounds[key];if(typeof v!=='number'||!Number.isFinite(v)||v<min||v>max)throw new Error(`PREFERENCES_PANEL: ${key} requires finite ${min}..${max} CSS pixels`);return v;};
|
|
25
30
|
export function parseWorkspacePanels(value:unknown):WorkspacePanels {const o=object(value,'panels');closed(o,Object.keys(panelBounds),'panels');return {leftWidth:panel(o.leftWidth,'leftWidth'),rightWidth:panel(o.rightWidth,'rightWidth'),inspectorHeight:panel(o.inspectorHeight,'inspectorHeight')};}
|
|
26
|
-
export function defaultWorkspacePreferences():WorkspacePreferences {return {schema:workspacePreferenceKey,revision:0,theme:'system',defaultCameraSync:true,defaultIdleRepeat:true,panels:{...defaultWorkspacePanels},stamps:Object.fromEntries(fields.map(field=>[field,{revision:0,writer:''}])) as Record<Field,FieldStamp>};}
|
|
31
|
+
export function defaultWorkspacePreferences():WorkspacePreferences {return {schema:workspacePreferenceKey,revision:0,theme:'system',defaultCameraSync:true,defaultIdleRepeat:true,settingsCollapsed:true,panels:{...defaultWorkspacePanels},stamps:Object.fromEntries(fields.map(field=>[field,{revision:0,writer:''}])) as Record<Field,FieldStamp>};}
|
|
27
32
|
const clone=(v:WorkspacePreferences):WorkspacePreferences=>({...v,panels:{...v.panels},stamps:Object.fromEntries(fields.map(field=>[field,{...v.stamps[field]}])) as Record<Field,FieldStamp>});
|
|
28
|
-
function leaf(value:WorkspacePreferences,field:Field):WorkspaceTheme|boolean|number {return field in panelBounds?value.panels[field as keyof WorkspacePanels]:value[field as 'theme'|
|
|
29
|
-
function put(value:WorkspacePreferences,field:Field,v:WorkspaceTheme|boolean|number):void {if(field==='theme')value.theme=theme(v);else if(field
|
|
33
|
+
function leaf(value:WorkspacePreferences,field:Field):WorkspaceTheme|boolean|number {return field in panelBounds?value.panels[field as keyof WorkspacePanels]:value[field as 'theme'|Flag];}
|
|
34
|
+
function put(value:WorkspacePreferences,field:Field,v:WorkspaceTheme|boolean|number):void {if(field==='theme')value.theme=theme(v);else if(flags.includes(field as Flag))value[field as Flag]=boolean(v,field);else value.panels[field as keyof WorkspacePanels]=panel(v,field as keyof WorkspacePanels);}
|
|
30
35
|
export function parseWorkspacePreferences(value:unknown):WorkspacePreferences {
|
|
31
|
-
const o=object(value,'preferences');closed(o,['schema','revision','theme','defaultCameraSync','defaultIdleRepeat','panels','stamps'],'preferences');if(o.schema!==workspacePreferenceKey)throw new Error('PREFERENCES_VERSION: unsupported schema');
|
|
32
|
-
const result=defaultWorkspacePreferences();result.theme=theme(o.theme);result.defaultCameraSync=boolean(o.defaultCameraSync,'defaultCameraSync');result.defaultIdleRepeat=boolean(o.defaultIdleRepeat,'defaultIdleRepeat');result.panels=parseWorkspacePanels(o.panels);
|
|
36
|
+
const o=object(value,'preferences');closed(o,['schema','revision','theme','defaultCameraSync','defaultIdleRepeat','settingsCollapsed','panels','stamps'],'preferences');if(o.schema!==workspacePreferenceKey)throw new Error('PREFERENCES_VERSION: unsupported schema');
|
|
37
|
+
const result=defaultWorkspacePreferences();result.theme=theme(o.theme);result.defaultCameraSync=boolean(o.defaultCameraSync,'defaultCameraSync');result.defaultIdleRepeat=boolean(o.defaultIdleRepeat,'defaultIdleRepeat');if('settingsCollapsed'in o)result.settingsCollapsed=boolean(o.settingsCollapsed,'settingsCollapsed');result.panels=parseWorkspacePanels(o.panels);
|
|
33
38
|
const stamps=object(o.stamps,'stamps');closed(stamps,fields,'stamps');
|
|
34
|
-
for(const field of fields){const stamp=object(stamps[field],`stamps.${field}`);closed(stamp,['revision','writer'],`stamps.${field}`);if(typeof stamp.revision!=='number'||!Number.isSafeInteger(stamp.revision)||stamp.revision<0||typeof stamp.writer!=='string'||!(/^[A-Za-z0-9_-]{1,80}$/.test(stamp.writer)||stamp.revision===0&&stamp.writer===''))throw new Error(`PREFERENCES_STAMP: invalid ${field} revision/writer`);if(stamp.revision===0&&(stamp.writer!==''||leaf(result,field)!==leaf(defaultWorkspacePreferences(),field)))throw new Error(`PREFERENCES_STAMP: unstamped ${field} differs from defaults`);result.stamps[field]={revision:stamp.revision,writer:stamp.writer};}
|
|
39
|
+
for(const field of fields){const stamp=stamps[field]===undefined&&addedFields.has(field)?{revision:0,writer:''}:object(stamps[field],`stamps.${field}`);closed(stamp,['revision','writer'],`stamps.${field}`);if(typeof stamp.revision!=='number'||!Number.isSafeInteger(stamp.revision)||stamp.revision<0||typeof stamp.writer!=='string'||!(/^[A-Za-z0-9_-]{1,80}$/.test(stamp.writer)||stamp.revision===0&&stamp.writer===''))throw new Error(`PREFERENCES_STAMP: invalid ${field} revision/writer`);if(stamp.revision===0&&(stamp.writer!==''||leaf(result,field)!==leaf(defaultWorkspacePreferences(),field)))throw new Error(`PREFERENCES_STAMP: unstamped ${field} differs from defaults`);result.stamps[field]={revision:stamp.revision,writer:stamp.writer};}
|
|
35
40
|
const revision=Math.max(...fields.map(field=>result.stamps[field].revision));if(o.revision!==revision)throw new Error('PREFERENCES_REVISION: record revision must equal maximum field revision');result.revision=revision;return result;
|
|
36
41
|
}
|
|
37
42
|
function parseText(raw:string):WorkspacePreferences {if(raw.length>8192)throw new Error('PREFERENCES_SIZE: record exceeds 8192 characters');return parseWorkspacePreferences(JSON.parse(raw));}
|
|
38
43
|
function patches(value:unknown):Map<Field,WorkspaceTheme|boolean|number> {
|
|
39
|
-
const o=object(value,'preference patch');closed(o,['theme','
|
|
40
|
-
if('theme'in o)patch.set('theme',theme(o.theme));for(const field of
|
|
44
|
+
const o=object(value,'preference patch');closed(o,['theme',...flags,'panels'],'preference patch');const patch=new Map<Field,WorkspaceTheme|boolean|number>();
|
|
45
|
+
if('theme'in o)patch.set('theme',theme(o.theme));for(const field of flags)if(field in o)patch.set(field,boolean(o[field],field));
|
|
41
46
|
if('panels'in o){const p=object(o.panels,'panel patch');closed(p,Object.keys(panelBounds),'panel patch');for(const key of Object.keys(p) as (keyof WorkspacePanels)[])patch.set(key,panel(p[key],key));}return patch;
|
|
42
47
|
}
|
|
43
48
|
/** Per-field Lamport stamps converge even when independent tabs wrote stale whole records.
|
package/web/workspace-ui.ts
CHANGED
|
@@ -41,7 +41,9 @@ export function bindWorkspaceUI(options:{changed:()=>void;defaultTheme:(theme:Wo
|
|
|
41
41
|
const header=[...left.children].find(node=>node.className.split(' ').includes('left-header'))??null;
|
|
42
42
|
const sourceList=document.createElement('div');sourceList.className='workspace-sources';sourceList.append(...[...left.childNodes].filter(node=>node!==header));left.append(sourceList);
|
|
43
43
|
const settings=document.createElement('section');settings.className='workspace-settings';settings.setAttribute('aria-label','Settings');
|
|
44
|
-
|
|
44
|
+
// The header holds the expand/collapse control; the body is hidden while collapsed (a workspace preference, default collapsed).
|
|
45
|
+
const settingsHeader=document.createElement('h2'),settingsToggle=document.createElement('button'),settingsBody=document.createElement('div');settingsHeader.className='sticky-header settings-header';settingsToggle.type='button';settingsToggle.id='settings-toggle';settingsToggle.className='settings-toggle';settingsToggle.textContent='Settings';settingsToggle.setAttribute('aria-controls','settings-body');settingsBody.id='settings-body';settingsBody.className='settings-body';settingsHeader.append(settingsToggle);settings.append(settingsHeader,settingsBody);
|
|
46
|
+
settingsBody.innerHTML='<label>Theme <select id="settings-theme" data-app-preference><option value="system">System</option><option value="dark">Dark</option><option value="light">Light</option></select></label><label><input id="settings-sync" data-app-preference type="checkbox">Sync cameras by default</label><label><input id="settings-idle" data-app-preference type="checkbox">Repeat idle by default</label><button id="settings-reset" type="button">Reset layout</button><p class="hint">Defaults apply to new inspections. Links and source drafts retain their explicit view.</p><p id="settings-state" class="hint" role="status"></p>';
|
|
45
47
|
sourceList.append(settings);
|
|
46
48
|
const theme=require<HTMLSelectElement>('settings-theme'),sync=require<HTMLInputElement>('settings-sync'),idle=require<HTMLInputElement>('settings-idle'),status=require('settings-state');
|
|
47
49
|
const store=new WorkspacePreferenceStore({onStatus:value=>{status.textContent=value.detail;}});let desired={...store.get().panels};
|
|
@@ -87,8 +89,8 @@ export function bindWorkspaceUI(options:{changed:()=>void;defaultTheme:(theme:Wo
|
|
|
87
89
|
layout.dataset.stageMinimum=String(effective.stageMinimumSatisfied);for(const binding of bindings)binding.refresh();options.changed();restore();
|
|
88
90
|
}finally{applying=false;}
|
|
89
91
|
}
|
|
90
|
-
const renderSettings=()=>{const value=store.get();theme.value=options.currentTheme?.()??value.theme;sync.checked=value.defaultCameraSync;idle.checked=value.defaultIdleRepeat;status.textContent=store.status().detail;};
|
|
91
|
-
theme.onchange=()=>{store.update({theme:theme.value as WorkspaceTheme});options.defaultTheme(store.get().theme);};sync.onchange=()=>{store.update({defaultCameraSync:sync.checked});};idle.onchange=()=>{store.update({defaultIdleRepeat:idle.checked});};require<HTMLButtonElement>('settings-reset').onclick=()=>store.resetLayout();
|
|
92
|
+
const renderSettings=()=>{const value=store.get();settingsToggle.setAttribute('aria-expanded',String(!value.settingsCollapsed));settingsBody.hidden=value.settingsCollapsed;settings.dataset.collapsed=String(value.settingsCollapsed);theme.value=options.currentTheme?.()??value.theme;sync.checked=value.defaultCameraSync;idle.checked=value.defaultIdleRepeat;status.textContent=store.status().detail;};
|
|
93
|
+
theme.onchange=()=>{store.update({theme:theme.value as WorkspaceTheme});options.defaultTheme(store.get().theme);};sync.onchange=()=>{store.update({defaultCameraSync:sync.checked});};idle.onchange=()=>{store.update({defaultIdleRepeat:idle.checked});};require<HTMLButtonElement>('settings-reset').onclick=()=>store.resetLayout();settingsToggle.onclick=()=>{store.update({settingsCollapsed:!store.get().settingsCollapsed});};
|
|
92
94
|
const unsubscribe=store.subscribe(value=>{desired={...value.panels};renderSettings();schedule();});renderSettings();
|
|
93
95
|
for(const tab of tabs){
|
|
94
96
|
const panel=panelFor(tab);let user=false,timer:ReturnType<typeof setTimeout>|undefined;
|