diff --git a/docs/reference/file-map.md b/docs/reference/file-map.md index 5871c2a..c2ab155 100644 --- a/docs/reference/file-map.md +++ b/docs/reference/file-map.md @@ -301,6 +301,7 @@ Wave 9 adds these: ```bash python tests/export_check.py # CR-008/CR-017 - export walk + hours guard 20 checks python tests/sample_check.py # S7 - one sample affordance, confirmed+fenced 10 checks +python tests/icon_check.py # S6 - one icon system, no emoji, mapped 5 checks ``` **Three probes were re-pointed at `T7.1`.** `sections_check.py` 5b drove the live diff --git a/docs/reference/tokens.md b/docs/reference/tokens.md index 200de90..a0b5919 100644 --- a/docs/reference/tokens.md +++ b/docs/reference/tokens.md @@ -961,3 +961,51 @@ Column `#` is the line number in `html/theme-light.css`. Read down each column p **120 declarations.** Lines 3–116 are the Carbon g10 set; lines 170–175 are the app-shell group added by the suite. Many share a literal by design — that is Carbon's v10→v11 alias layer, not the `S5` defect. See the note at the end of §3. + +## Icons (S6 / T9.3) + +One system: **monochrome text-presentation glyphs**, chosen because the suite +is classic-script vanilla HTML with no bundler — an SVG sprite or icon font is +a new asset pipeline, while text glyphs render through the same font stack as +the words beside them. The trade accepted: text glyphs vary slightly by font, +but never flip into per-platform colour artwork the way emoji do, which is the +failure S6 names. **No emoji anywhere in the UI**; `tests/icon_check.py` +sweeps every page for emoji-range codepoints and the U+FE0F emoji-presentation +selector on every run. + +One meaning per glyph, one glyph per meaning: + +| Meaning | Glyph | Codepoint | Notes | +|---|---|---|---| +| done / ok / success | ✓ | U+2713 | replaced emoji ✅ | +| close / remove / failure | ✕ | U+2715 | replaced emoji ❌ | +| needs attention / warning | ⚠ | U+26A0 | text presentation | +| blocked / on hold | ⊘ | U+2298 | replaced emoji ⛔ | +| edit with a logged reason | ✎ | U+270E | replaced emoji 🔒; same meaning on sign-off overrides | +| revert / refresh | ↺ / ↻ | U+21BA / U+21BB | direction distinguishes undo from reload | +| settings / admin | ⚙ | U+2699 | replaced ⚡ on the side nav | +| export / download | ⤓ | U+2913 | | +| import / upload | ⤒ | U+2912 | | +| key point (help copy) | ◆ | U+25C6 | replaced emoji ⭐ | +| reference link | ▸ | U+25B8 | replaced emoji 📁 | +| info tooltip badge | ⓘ | U+24D8 | the `.help-tip` component (S8/T9.5) | +| back / navigation | ← ‹ › → | U+2190 U+2039 U+203A U+2192 | | +| views (nav) | ◔ ▤ ▦ ▧ | U+25D4 U+25A4 U+25A6 U+25A7 | one per view, never reused | +| drag handle | ⣿ | U+283F | sequence reordering | +| home | ⌂ | U+2302 | side nav | +| search | ⌕ | U+2315 | app bar | +| print / save PDF | ⎙ | U+2399 | | +| power / sign out | ⏻ | U+23FB | side nav | +| sort direction | ▲ ▼ | U+25B2 U+25BC | column headers, paired | +| in the QA queue | ◉ | U+25C9 | the CR-014 banner | +| clock / language & time | ◷ | U+25F7 | replaced ⌚ U+231A, which is emoji-presentation by default | +| menu (off-canvas) | ☰ | U+2630 | app bar | +| people / directory | ☺ | U+263A | side nav; text presentation | +| field view | ⚒ | U+2692 | side nav; text presentation | +| password / key | ⚿ | U+26BF | side nav | + +Dropped rather than mapped: ⚡ on action buttons (the label carries the +action), 📷 on “Add photo” (labelled), 📄/🖼 on file rows (the filename is the +label; a pictogram repeating “this is a file” said nothing). Every remaining +glyph sits beside visible text or carries an accessible name — none is the +only carrier of meaning. diff --git a/html/admin.js b/html/admin.js index dc20c26..7805658 100644 --- a/html/admin.js +++ b/html/admin.js @@ -36,13 +36,13 @@ async function checkHealth(){ b.className='banner'; b.textContent='Checking…'; const { status, json } = await api('GET','/api/health'); if(status===200 && json && json.ok){ - b.className='banner ok'; b.textContent='✅ API reachable — /api/health returned ok.'; + b.className='banner ok'; b.textContent='✓ API reachable — /api/health returned ok.'; } else if(status===404){ - b.className='banner bad'; b.textContent='❌ /api/ returns 404 — the reverse proxy is not routing /api/ to the API. The site loads but the API is unreachable from the browser.'; + b.className='banner bad'; b.textContent='✕ /api/ returns 404 — the reverse proxy is not routing /api/ to the API. The site loads but the API is unreachable from the browser.'; } else if(status===0){ - b.className='banner bad'; b.textContent='❌ Could not reach the server: '+json; + b.className='banner bad'; b.textContent='✕ Could not reach the server: '+json; } else { - b.className='banner bad'; b.textContent='❌ Unexpected response: HTTP '+status; + b.className='banner bad'; b.textContent='✕ Unexpected response: HTTP '+status; } } @@ -121,9 +121,9 @@ function stdConstraints(open){ return ['Safety & Permitting','Quality Control / async function seedDemo(){ const o=document.getElementById('demo-out'); o.innerHTML=''; let r = await api('GET','/api/health'); - if(!(r.status===200 && r.json && r.json.ok)){ demoLog('❌ API unreachable — fix /api/ routing first.'); return; } + if(!(r.status===200 && r.json && r.json.ok)){ demoLog('✕ API unreachable — fix /api/ routing first.'); return; } r = await api('POST','/api/projects',{name:'DEMO — Micron INC (test data)',number:'DEMO-001',client:'Micron Technology, Inc.',division:'Semiconductor',site:'Boise, ID — Fab',created_by:'admin-console'}); - if(r.status!==200){ demoLog('❌ create project failed (HTTP '+r.status+')'); return; } + if(r.status!==200){ demoLog('✕ create project failed (HTTP '+r.status+')'); return; } const pid=r.json.id; demoLog('Project created: '+r.json.name); r = await api('POST','/api/sops',{project_id:pid,name:'DEMO SOP',number:'DEMO-001',complete:true,data:{governance:{woFormat:'WP##-[Sector]-[TYPE]',disciplines:['Mechanical','Electrical','Tech'],discMode:'choice',instanceSuffix:'letter',woSize:'Standard — 3–5 days (≈40–80 hrs)',sizeHoursMax:'80'}}}); const sid=r.json && r.json.id; demoLog('SOP created (complete).'); @@ -142,7 +142,7 @@ async function seedDemo(){ await mk('WP05-3P-PANEL','3P panel install','Panel Install','Draft',{disciplines:['Electrical'],hours:'120',constraints:stdConstraints(['Schedule']),due:'2026-07-20'}); r = await api('GET','/api/wps/metrics?project_id='+pid); demoLog('\nMetrics (masters excluded): '+JSON.stringify(r.json)); - demoLog('\n✅ Done — "DEMO — Micron INC (test data)" now appears in the home picker.'); + demoLog('\n✓ Done — "DEMO — Micron INC (test data)" now appears in the home picker.'); snapshot(); } async function cleanDemo(){ @@ -151,11 +151,11 @@ async function cleanDemo(){ // archived=all, or an archived DEMO-/SMOKE- project becomes unreachable from // this button — the default list hides it and nothing else here can delete it. const r = await api('GET','/api/projects?archived=all'); - if(r.status!==200){ demoLog('❌ API unreachable (HTTP '+r.status+').'); return; } + if(r.status!==200){ demoLog('✕ API unreachable (HTTP '+r.status+').'); return; } const targets=(r.json||[]).filter(p=>/^(DEMO-|SMOKE-)/.test(String(p.number||''))); if(!targets.length){ demoLog('Nothing to remove.'); return; } for(const p of targets){ await api('DELETE','/api/projects/'+p.id); demoLog('Deleted: '+p.name+' ('+p.number+')'); } - demoLog('\n✅ Removed '+targets.length+' project(s).'); + demoLog('\n✓ Removed '+targets.length+' project(s).'); snapshot(); } @@ -175,14 +175,14 @@ async function loadProjects(){ const { status, json } = await api('GET','/api/projects?archived=all'); if(status===403){ banner.className='banner bad'; - banner.textContent='❌ Your account is not an admin, so you can’t archive or delete projects here.'; + banner.textContent='✕ Your account is not an admin, so you can’t archive or delete projects here.'; wrap.innerHTML=''; return; } if(status===401){ - banner.className='banner bad'; banner.textContent='❌ Not signed in. Reload and log in again.'; wrap.innerHTML=''; return; + banner.className='banner bad'; banner.textContent='✕ Not signed in. Reload and log in again.'; wrap.innerHTML=''; return; } if(status!==200 || !Array.isArray(json)){ - banner.className='banner bad'; banner.textContent='❌ Could not load projects (HTTP '+status+').'; wrap.innerHTML=''; return; + banner.className='banner bad'; banner.textContent='✕ Could not load projects (HTTP '+status+').'; wrap.innerHTML=''; return; } banner.style.display='none'; _adminProjects = json; @@ -284,14 +284,14 @@ async function loadDefaultMembers(){ const { status, json } = await api('GET','/api/auth/users'); if(status===403){ banner.className='banner bad'; - banner.textContent='❌ Your account is not an admin, so you can’t change who is added to new projects.'; + banner.textContent='✕ Your account is not an admin, so you can’t change who is added to new projects.'; wrap.innerHTML=''; return; } if(status===401){ - banner.className='banner bad'; banner.textContent='❌ Not signed in. Reload and log in again.'; wrap.innerHTML=''; return; + banner.className='banner bad'; banner.textContent='✕ Not signed in. Reload and log in again.'; wrap.innerHTML=''; return; } if(status!==200 || !Array.isArray(json)){ - banner.className='banner bad'; banner.textContent='❌ Could not load users (HTTP '+status+').'; wrap.innerHTML=''; return; + banner.className='banner bad'; banner.textContent='✕ Could not load users (HTTP '+status+').'; wrap.innerHTML=''; return; } banner.style.display='none'; _defMemUsers = json; @@ -560,7 +560,7 @@ async function saveLocalization(){ const m = document.getElementById('l10n-msg'); if(m){ m.textContent = 'Saved.'; m.style.color = 'var(--green)'; } } else { - msg.textContent = '❌ '+((json && json.detail) || ('HTTP '+status)); + msg.textContent = '✕ '+((json && json.detail) || ('HTTP '+status)); msg.style.color = 'var(--red)'; } } @@ -627,8 +627,8 @@ async function saveSettings(){ async function testEmail(){ const msg = document.getElementById('set-msg'); msg.textContent = 'Sending test…'; msg.style.color = 'var(--muted)'; const { status, json } = await api('POST','/api/settings/test-email', {}); - if(status===200) { msg.textContent = '✅ Test sent to '+((json&&json.to)||'you')+'.'; msg.style.color = 'var(--green)'; } - else { msg.textContent = '❌ '+((json && json.detail) || ('HTTP '+status)); msg.style.color = 'var(--red)'; } + if(status===200) { msg.textContent = '✓ Test sent to '+((json&&json.to)||'you')+'.'; msg.style.color = 'var(--green)'; } + else { msg.textContent = '✕ '+((json && json.detail) || ('HTTP '+status)); msg.style.color = 'var(--red)'; } } async function loadNotifications(){ const box = document.getElementById('notif-box'); if(!box) return; diff --git a/html/field.js b/html/field.js index 090edcf..53d9ad9 100644 --- a/html/field.js +++ b/html/field.js @@ -149,11 +149,11 @@ function renderDetail() { (((p.files) || []).length ? '
From the home page, open SOP Configuration, the Work Package Creator, or the Dashboard. Inside the suite, switch any time using the top tabs: ⚙️ SOP Configuration, 📋 Work Package Creation, and 📊 Dashboard. The active project and SOP follow you across all of them.
+From the home page, open SOP Configuration, the Work Package Creator, or the Dashboard. Inside the suite, switch any time using the top tabs: ⚙SOP Configuration, Work Package Creation, and Dashboard. The active project and SOP follow you across all of them.
Save Draft stores the package; ⚡ Save & View saves and renders the print-ready output. Drafts auto-save to your browser as you type, so nothing is lost if you close the tab.
` }, +Save Draft stores the package; Save & View saves and renders the print-ready output. Drafts auto-save to your browser as you type, so nothing is lost if you close the tab.
` }, { id: 'statuses', title: 'Statuses', body: `The dashboard aggregates every (non-master) package in the active project. Open it from the home page, the suite's 📊 Dashboard tab, or the Creator header.
+The dashboard aggregates every (non-master) package in the active project. Open it from the home page, the suite's Dashboard tab, or the Creator header.
Shows WP #, subject, type, discipline, status, Gates (clear, n open, or master), due date (red if overdue), and hours. Row actions: issue (when release-ready), view, and edit. Filter with the search box and the status / discipline dropdowns.
@@ -241,7 +241,7 @@ { id: 'data', title: 'Samples, sharing & comments', body: `⭐ Load sample is context-aware: on the SOP tab it loads a complete sample SOP; on the WP tab it loads an example Work Package. Great for learning the tool or demoing.
+Load sample data is context-aware: on the SOP tab it loads a complete sample SOP; on the WP tab it loads an example Work Package. Great for learning the tool or demoing.
Leave feedback from the home page, per-step comments in the SOP tool (💬 Step Comments), or package comments in the Creator's 💬 Comments drawer. Comments are saved and can be exported/imported as .json so reviewers can share them — and, when the API is reachable, they're collected centrally too.
Leave feedback from the home page, per-step comments in the SOP tool (Step Comments), or package comments in the Creator's Comments drawer. Comments are saved and can be exported/imported as .json so reviewers can share them — and, when the API is reachable, they're collected centrally too.
📊 Usage Logs / ▤ Usage Data shows session and event counts and can export the full log. A dev-mode toggle pauses tracking during demos.
` }, +Usage Logs / ▤ Usage Data shows session and event counts and can export the full log. A dev-mode toggle pauses tracking during demos.
` }, { id: 'shortcuts', title: 'Tips & shortcuts', body: `