@gamaze/hicortex 0.20.10 → 0.21.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.
Files changed (2) hide show
  1. package/assets/dashboard.html +202 -16
  2. package/package.json +1 -1
@@ -6,6 +6,12 @@
6
6
  visual on real endpoints, zero new write paths (Update/Remove ride the
7
7
  existing /update + /delete; identity Save rides PUT /identity).
8
8
 
9
+ NAMING (owner ruling 2026-09-14): this surface is "the Hicortex server
10
+ dashboard" — the word "console" never appears in USER-VISIBLE strings
11
+ (titles, tooltips, aria-labels, placeholders, toasts, empty states).
12
+ Historical "console" wording in these comments is internal shorthand and
13
+ stays.
14
+
9
15
  PORTED FROM mockups/409-r6-flight.html (the design of record — its banner
10
16
  documents every behavior; the engine below is that page with the seeded
11
17
  data layer replaced by fetches). Live-data substitutions:
@@ -87,6 +93,28 @@
87
93
  within ~2–2.5× (owner: no huge green balls, nothing barely visible);
88
94
  stage colors untouched.
89
95
 
96
+ FIX ROUND 6 (owner-approved proto of record mockups/progress-bar.html,
97
+ 2026-09-14 — variant 3 + copy A): ENTRY LOADING SCREEN. Pure inline
98
+ HTML+CSS at the very top of the body, BEFORE the app markup and the
99
+ engine script — it paints on URL entry with ZERO script (the real
100
+ page is black for seconds while the engine parses/boots; the splash
101
+ must beat that). All motion is opacity/transform only, so the
102
+ compositor keeps it animating straight through the engine's main-
103
+ thread freeze. Fully opaque black in BOTH themes (the proto's light
104
+ "paper twin" was review chrome, not shipped behavior): a breathing
105
+ green dot above an indeterminate flowing line (two brand-orange
106
+ segments, uneven pace, looping — never a percentage, never an ETA)
107
+ under the copy line "Entering the Hicortex fleet brain". The engine
108
+ only ever fades it away: entrySplashDismiss hooks the boot REVEAL —
109
+ the existing single handover (data landed → world built → app
110
+ unhidden), NOT DOMContentLoaded — fades .4s, then removes the node.
111
+ Failure seams swap the copy line honestly and never loop silently:
112
+ both core sources rejected → "could not reach the Hicortex server…"
113
+ (held readable ≥2.8 s); boot() throws → same treatment + the static
114
+ shell; 30 s without any settlement → "still waiting…" (no dismiss —
115
+ data may still land). #still removes it synchronously (screenshot
116
+ contract); reduced motion rests on the bar's static base transform.
117
+
90
118
  FIX ROUND 3 (owner findings on the live console, 2026-09-14):
91
119
  - FIELD LINKS (owner: "the white lines … get very thick — they kind of
92
120
  overtake most of the image"): settled strokes are hairline-floored and
@@ -442,6 +470,22 @@
442
470
  padding:2px 11px; white-space:nowrap; width:fit-content;
443
471
  }
444
472
  #lim-ag .limit-cta:hover{ border-color:var(--amber); }
473
+ /* #409 fix round 4 — the Model card's configure pill: the mock of record
474
+ puts it at the card's BOTTOM-RIGHT, grey/muted (the neutral twin of the
475
+ Agents Connect pill above). margin-top:auto pins it to the card's foot
476
+ however tall the row stretches — row-2 siblings flex-fill, so
477
+ align-self:flex-end alone would leave it floating mid-card. The
478
+ :not([hidden]) guard keeps the degrade (old server / GET failure)
479
+ working: a plain display rule would override the hidden attribute. All
480
+ colors ride theme vars — both themes come free. */
481
+ #model-card .limit-cta:not([hidden]){
482
+ display:inline-block; align-self:flex-end; margin-top:auto;
483
+ color:var(--dim); border:1px solid var(--line2);
484
+ background:color-mix(in srgb, var(--text) 4%, transparent);
485
+ font-size:10.5px; font-weight:600; border-radius:999px;
486
+ padding:2px 11px; white-space:nowrap; width:fit-content;
487
+ }
488
+ #model-card .limit-cta:not([hidden]):hover{ border-color:var(--dim); color:var(--text); }
445
489
 
446
490
  /* ---- row-2 operator cards share the .bseg base; .gh is THE card-header
447
491
  component (titles + non-numeric descriptors only) ---- */
@@ -943,6 +987,62 @@
943
987
  </script>
944
988
  </head>
945
989
  <body class="booting">
990
+ <!-- ============ entry loading screen (#409 fix round 6 — the owner-
991
+ approved proto of record mockups/progress-bar.html, variant 3 + copy
992
+ A). Sits at the very top of the body with its OWN style block: it paints
993
+ on URL entry with ZERO script, fully opaque black in both themes, and
994
+ the engine only ever fades it away (entrySplashDismiss at the boot
995
+ reveal). On failure the copy line swaps to an honest error — never an
996
+ infinite loop. All motion is opacity/transform only: the compositor
997
+ keeps it animating while the engine below blocks the main thread. ============ -->
998
+ <style>
999
+ #entry-load{
1000
+ position:fixed; inset:0; z-index:1000; background:#0a0a0a;
1001
+ display:flex; flex-direction:column; align-items:center; justify-content:center; gap:17px;
1002
+ transition:opacity .4s ease;
1003
+ }
1004
+ #entry-load.gone{ opacity:0; pointer-events:none; }
1005
+ /* variant 3's dot: the header healthy mark's look, breathing */
1006
+ #entry-load .el-dot{
1007
+ width:10px; height:10px; border-radius:50%;
1008
+ background:radial-gradient(circle at 35% 35%, color-mix(in srgb, #3dff7a 95%, white), color-mix(in srgb, #3dff7a 25%, transparent) 55%, transparent 70%);
1009
+ box-shadow:0 0 12px color-mix(in srgb, #3dff7a 55%, transparent);
1010
+ animation:el-breathe 3.4s ease-in-out infinite;
1011
+ }
1012
+ @keyframes el-breathe{ 0%,100%{opacity:1} 50%{opacity:.5} }
1013
+ #entry-load .el-line{ font-size:13.5px; color:#9a9a9a; text-align:center; padding:0 24px; }
1014
+ /* the indeterminate line: two brand-orange segments on a faint track,
1015
+ uneven pace, varying length, looping forever; the base transform is
1016
+ the reduced-motion rest pose. Never a percentage, never an ETA. */
1017
+ #entry-load .el-bar{
1018
+ width:min(300px,72%); height:2.5px; border-radius:99px;
1019
+ background:#1e1e1e; overflow:hidden; position:relative;
1020
+ }
1021
+ #entry-load .el-bar i{
1022
+ position:absolute; top:0; bottom:0; left:0; width:100%;
1023
+ background:#ff9f43; border-radius:99px;
1024
+ transform-origin:0 50%; transform:translateX(32%) scaleX(.42);
1025
+ box-shadow:0 0 8px color-mix(in srgb, #ff9f43 45%, transparent);
1026
+ }
1027
+ #entry-load .el-bar i.a{ animation:el-indet-a 2.2s cubic-bezier(.55,.1,.7,.5) infinite backwards; }
1028
+ #entry-load .el-bar i.b{ opacity:.6; animation:el-indet-b 2.2s cubic-bezier(.65,.05,.75,.5) 1.15s infinite backwards; }
1029
+ @keyframes el-indet-a{
1030
+ 0% { transform:translateX(-38%) scaleX(.12); animation-timing-function:cubic-bezier(.6,.05,.8,.5); }
1031
+ 55% { transform:translateX(46%) scaleX(.62); animation-timing-function:cubic-bezier(.25,.65,.35,1); }
1032
+ 80% { transform:translateX(84%) scaleX(.68); animation-timing-function:cubic-bezier(.55,.05,.8,.45); }
1033
+ 100% { transform:translateX(140%) scaleX(.16); }
1034
+ }
1035
+ @keyframes el-indet-b{
1036
+ 0% { transform:translateX(-24%) scaleX(.08); animation-timing-function:cubic-bezier(.65,.05,.8,.5); }
1037
+ 70% { transform:translateX(52%) scaleX(.30); animation-timing-function:cubic-bezier(.2,.6,.45,1); }
1038
+ 100% { transform:translateX(116%) scaleX(.10); }
1039
+ }
1040
+ </style>
1041
+ <div id="entry-load" role="status" aria-live="polite">
1042
+ <span class="el-dot" aria-hidden="true"></span>
1043
+ <div class="el-line" id="entry-load-line">Entering the Hicortex fleet brain</div>
1044
+ <div class="el-bar" aria-hidden="true"><i class="a"></i><i class="b"></i></div>
1045
+ </div>
946
1046
  <header>
947
1047
  <div class="h-left">
948
1048
  <div class="h-search">
@@ -1045,10 +1145,11 @@
1045
1145
  <div id="feel-rows"></div>
1046
1146
  </div>
1047
1147
  <div class="bseg c-grey" id="model-card">
1048
- <div class="gh"><span class="t">Model</span><button class="chip" id="model-cfg" hidden title="edit the model knobs (config.json llm* keys)">configure</button></div>
1049
- <div class="big"><span class="n" id="model-provider">—</span><span class="l" id="model-tail"></span></div>
1148
+ <div class="gh"><span class="t">Model</span></div>
1149
+ <div class="big"><span class="n" id="model-provider">—</span></div>
1050
1150
  <div class="csub mono" id="model-id">&nbsp;</div>
1051
1151
  <div class="csub">detected from config</div>
1152
+ <button class="limit-cta" id="model-cfg" hidden title="edit the model knobs (config.json llm* keys)">configure &#8594;</button>
1052
1153
  </div>
1053
1154
  </div>
1054
1155
  </div>
@@ -1151,9 +1252,57 @@
1151
1252
  </div>
1152
1253
  <script>
1153
1254
  'use strict';
1255
+ /* =====================================================================
1256
+ ENTRY SPLASH (#409 fix round 6) — the loading screen is already painted
1257
+ by the static markup at the top of the body; the engine only ever fades it
1258
+ away. entrySplashDismiss hooks the boot REVEAL (reveal() in boot(): the
1259
+ single handover — data landed, world built, app unhidden — NOT
1260
+ DOMContentLoaded). State rides the node's dataset so both functions stay
1261
+ pure w.r.t. the DOM (extract-and-run test convention). Failure seams
1262
+ swap the copy line honestly and NEVER loop silently: total data failure
1263
+ holds the error line readable >= ENTRY_ERROR_HOLD before fading (even if
1264
+ the reveal fires earlier — and on its own if the reveal never comes); a
1265
+ boot() throw does the same and clears the booting class; a 30 s deadline
1266
+ (below) flags a server that never answered without dismissing — data
1267
+ may still land, and the reveal then owns the dismissal.
1268
+ ===================================================================== */
1269
+ const ENTRY_FAIL_COPY='could not reach the Hicortex server — check that it is running, then reload';
1270
+ const ENTRY_BOOTFAIL_COPY='the dashboard failed to start — reload to retry';
1271
+ const ENTRY_SLOW_COPY='still waiting for the server — it has not answered yet';
1272
+ const ENTRY_ERROR_HOLD=2800; /* ms the failure line stays readable */
1273
+ function entrySplashFail(msg){
1274
+ const s=document.getElementById('entry-load'), l=document.getElementById('entry-load-line');
1275
+ if(!s || !l || s.dataset.gone==='1') return;
1276
+ l.textContent=msg;
1277
+ s.dataset.failed='1'; s.dataset.failAt=String(Date.now());
1278
+ setTimeout(entrySplashDismiss, ENTRY_ERROR_HOLD); /* owns the beat even if the reveal never comes */
1279
+ }
1280
+ function entrySplashDismiss(){
1281
+ const s=document.getElementById('entry-load');
1282
+ if(!s) return;
1283
+ if(s.dataset.failed==='1'){ /* hold the error line readable */
1284
+ const remain=ENTRY_ERROR_HOLD-(Date.now()-Number(s.dataset.failAt||0));
1285
+ if(remain>0){ setTimeout(entrySplashDismiss, remain); return; }
1286
+ }
1287
+ if(s.dataset.gone==='1') return; /* idempotent */
1288
+ s.dataset.gone='1';
1289
+ const instant=document.body.classList.contains('instant') ||
1290
+ (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches);
1291
+ if(instant){ s.remove(); return; } /* #still / reduced motion: no fade */
1292
+ s.classList.add('gone');
1293
+ s.addEventListener('transitionend', function(){ s.remove(); }, {once:true});
1294
+ setTimeout(function(){ s.remove(); }, 900); /* fallback — transitionend is not guaranteed */
1295
+ }
1296
+ /* honest slow-wait flag: boot fetches still unsettled 30 s in → say so on
1297
+ the splash (no dismissal — the reveal still owns that if data lands) */
1298
+ setTimeout(function(){
1299
+ const s=document.getElementById('entry-load'), l=document.getElementById('entry-load-line');
1300
+ if(s && l && s.dataset.gone!=='1' && s.dataset.failed!=='1') l.textContent=ENTRY_SLOW_COPY;
1301
+ }, 30000);
1302
+
1154
1303
  /* #still (or ?still): skip the boot reveal — deterministic screenshots/tests. */
1155
1304
  const STILL = /(?:#|[?&])still\b/.test(location.hash + ' ' + location.search);
1156
- if(STILL){ document.body.classList.remove('booting'); document.body.classList.add('instant'); }
1305
+ if(STILL){ document.body.classList.remove('booting'); document.body.classList.add('instant'); entrySplashDismiss(); }
1157
1306
 
1158
1307
  /* =====================================================================
1159
1308
  The approved #409 r6-flight engine, ported to live data. One canvas,
@@ -2904,26 +3053,49 @@ function renderRecBand(ups){
2904
3053
  }
2905
3054
 
2906
3055
  let MODEL_LABEL='';
3056
+ /* #409 fix round 5 — owner ruling 2026-09-14 (verbatim): "I do NOT want to
3057
+ know what it is compatible with, I want to know the fucking model." The
3058
+ card face shows THE MODEL, nothing else: hero = the namespace-stripped
3059
+ model name (fit-scaled, floor 20px), mono line = the raw config id,
3060
+ caption "detected from config", configure pill. NO protocol line, NO
3061
+ provider line — local and cloud endpoints render identically. (The
3062
+ configure modal keeps its technical fields: that is the operator surface.)
3063
+ The degrade fires ONLY when there is no model AT ALL — neither the config
3064
+ echo (M.model) nor the runtime label (/health/detail) carries one. A
3065
+ configured model with null provider/backend still renders: the daemon may
3066
+ legitimately run no LLM (resolveExplicitLlmConfig requires llmApiKey
3067
+ alongside llmBaseUrl; a keyless local endpoint is recall-only) while the
3068
+ config declares the model — the card keys on the MODEL, not the runtime. */
2907
3069
  function renderModelCard(){
2908
3070
  /* configure pill only when GET /dashboard/model succeeded (M truthy) —
2909
3071
  an old server / load error degrades to the phase-1 label-only card */
2910
3072
  el('model-cfg').hidden=!M;
2911
- if(!MODEL_LABEL||MODEL_LABEL==='not configured'){
3073
+ const hero=el('model-provider');
3074
+ hero.style.fontSize=''; // undo a previous hero fit (below)
3075
+ /* The config echo is the display truth (the RAW config id, never a
3076
+ provider-prefixed composition); the runtime label is only the fallback
3077
+ for pre-#422 servers without /dashboard/model. */
3078
+ const raw=(M&&M.model)||(MODEL_LABEL&&MODEL_LABEL!=='not configured'?MODEL_LABEL:'');
3079
+ if(!raw){
2912
3080
  /* #409 fix round — the no-LLM state says so ON THE FACE (a muted hint),
2913
3081
  not a bare dash: correct degraded state, self-explanatory rendering */
2914
- el('model-provider').textContent='no model configured';
2915
- el('model-provider').classList.add('noconf');
2916
- el('model-tail').textContent='';
3082
+ hero.textContent='no model configured';
3083
+ hero.classList.add('noconf');
2917
3084
  el('model-id').textContent='';
2918
3085
  return;
2919
3086
  }
2920
- el('model-provider').classList.remove('noconf');
2921
- const slash=MODEL_LABEL.indexOf('/');
2922
- const provider=slash>0?MODEL_LABEL.slice(0,slash):MODEL_LABEL;
2923
- const model=slash>0?MODEL_LABEL.slice(slash+1):'';
2924
- el('model-provider').textContent=provider;
2925
- el('model-tail').textContent=model?trunc(model,18):'';
2926
- el('model-id').textContent=MODEL_LABEL;
3087
+ hero.classList.remove('noconf');
3088
+ const ns=raw.lastIndexOf('/');
3089
+ hero.textContent=ns>=0?raw.slice(ns+1):raw; // the model name = hero
3090
+ /* Long ids are the norm (mlx-community/Qwen3.8-27B-4bit et al); the hero
3091
+ NEVER clips — it scales down to the slot (floor 20px) while the full id
3092
+ rides the mono line. One pass is exact: text width is linear in
3093
+ font-size. */
3094
+ if(hero.scrollWidth>hero.clientWidth){
3095
+ const fs=parseFloat(getComputedStyle(hero).fontSize);
3096
+ hero.style.fontSize=Math.max(20,Math.floor(fs*hero.clientWidth/hero.scrollWidth))+'px';
3097
+ }
3098
+ el('model-id').textContent=raw;
2927
3099
  }
2928
3100
 
2929
3101
  /* ---------- model settings modal (#422 phase 2) — the configure pill's
@@ -4194,6 +4366,12 @@ async function boot(){
4194
4366
  if(hd.status==='fulfilled'&&hd.value&&typeof hd.value.llm==='string') MODEL_LABEL=hd.value.llm;
4195
4367
  else if(hd.status==='fulfilled'&&hd.value&&hd.value.model) MODEL_LABEL=String(hd.value.model);
4196
4368
  if(hh.status==='fulfilled'&&hh.value&&hh.value.status) { /* health body shape varies; the poll handles the dot */ }
4369
+ /* entry splash (#409 fix round 6): BOTH core sources rejected = total
4370
+ failure — swap the splash line to the honest error (the dismissal holds
4371
+ it readable, then fades into this degraded page, whose own error
4372
+ surfaces — the toast above, the unreachable dot — carry the detail). */
4373
+ if(dd.status==='rejected' && ff.status==='rejected')
4374
+ entrySplashFail(ENTRY_FAIL_COPY);
4197
4375
 
4198
4376
  buildWorld(); // no-op when F failed — the empty field still renders
4199
4377
  buildPaths();
@@ -4219,13 +4397,21 @@ async function boot(){
4219
4397
  loadAccount();
4220
4398
  pollHealth();
4221
4399
 
4222
- const reveal=()=>{ booting=false; document.body.classList.remove('booting'); dirty=true; };
4400
+ const reveal=()=>{ booting=false; document.body.classList.remove('booting'); dirty=true; entrySplashDismiss(); };
4223
4401
  if(REDUCED||STILL) reveal();
4224
4402
  else setTimeout(reveal, 1400);
4225
4403
  setTimeout(()=>{ const h=el('hint'); if(h) h.style.opacity='0'; }, 9500);
4226
4404
  setTimeout(()=>{ if(!REDUCED&&!STILL){ spawnSignal(true); nextSpawnAt=performance.now()+4200; } }, (REDUCED||STILL)?0:2650);
4227
4405
  }
4228
- boot();
4406
+ boot().catch(err=>{
4407
+ /* a thrown boot must never leave the splash looping: honest line on the
4408
+ splash, the static shell visible after the readable hold, toast per the
4409
+ page's error pattern (the catch mirrors reveal minus the redraw). */
4410
+ try{ console.error('dashboard boot failed', err); }catch(e2){}
4411
+ booting=false; document.body.classList.remove('booting');
4412
+ toast('Boot failed — '+(err && err.message ? err.message : 'unknown error'));
4413
+ entrySplashFail(ENTRY_BOOTFAIL_COPY);
4414
+ });
4229
4415
  rafId=requestAnimationFrame(frame);
4230
4416
  resize();
4231
4417
  draw(performance.now(),16); /* one synchronous paint — the field exists before the first rAF */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gamaze/hicortex",
3
- "version": "0.20.10",
3
+ "version": "0.21.0",
4
4
  "description": "Persistent agent identity for AI agents \u2014 a hand-edited identity layer, nightly-distilled experience, and lessons injected every session, shared across your whole fleet. Works with Hermes, OpenClaw, Claude Code, Pi, and opencode.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {