@vale-dinap/lucidly 0.3.0 → 0.5.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/web/index.html CHANGED
@@ -9,6 +9,7 @@
9
9
  <script src="theme.js"></script>
10
10
  </head>
11
11
  <body>
12
+ <nav class="tabs" id="tabs" role="tablist" aria-label="Open sessions" hidden></nav>
12
13
  <header class="top">
13
14
  <div class="brand"><img src="favicon.svg" alt="" width="20" height="20" /><span>Lucidly</span></div>
14
15
  <button class="ibtn open-btn" id="sessionsBtn" title="Open session (O)" aria-label="Open session">
@@ -28,6 +29,12 @@
28
29
  </button>
29
30
  <div class="pop wake-pop" id="wakePop" hidden></div>
30
31
  </div>
32
+ <div class="wake-wrap">
33
+ <button class="ibtn snd" id="soundBtn" aria-haspopup="dialog" aria-label="Sound alerts" title="Sound alerts">
34
+ <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6.5 16.5V11a5.5 5.5 0 0 1 11 0v5.5l1.5 2h-14z M10 20.5a2.1 2.1 0 0 0 4 0" /><path class="waves" d="M20 7.5a6 6 0 0 1 1 3.5 M4 7.5a6 6 0 0 0-1 3.5" /><path class="mute" d="M4 4l16 16" /></svg>
35
+ </button>
36
+ <div class="pop wake-pop" id="soundPop" hidden></div>
37
+ </div>
31
38
  <div class="grow"></div>
32
39
  <div class="meter-wrap">
33
40
  <span class="pill warn" id="outdated" hidden title="Lucidly's engine was updated after this server started. Stop it and run lucidly again to apply the update: some data (such as token counts) may be missing until then.">Restart Lucidly to update</span>
@@ -102,7 +109,7 @@
102
109
 
103
110
  <dialog id="picker" class="picker">
104
111
  <form method="dialog" class="picker-h">
105
- <h3>Open session</h3>
112
+ <h3 id="pickerTitle">Open session</h3>
106
113
  <nav class="seg">
107
114
  <button type="button" data-scope="project" class="on">This project</button>
108
115
  <button type="button" data-scope="all">All projects</button>
@@ -111,6 +118,7 @@
111
118
  </form>
112
119
  <div id="pickerOpen"></div>
113
120
  <ul id="pickerList" class="plist"></ul>
121
+ <div class="picker-hint" id="pickerHint"></div>
114
122
  <label class="picker-f" title="A file saved with the download button: keeps a run after its transcript is gone, or lets someone else open it">
115
123
  <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 20V9m0 0l-4 4m4-4l4 4M5 4h14" /></svg>
116
124
  Open a saved snapshot…
package/web/style.css CHANGED
@@ -1118,8 +1118,55 @@ pre .dh { color: var(--faint); font-weight: 600; }
1118
1118
  @keyframes gx-breathe { 0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--oc) 32%, transparent); } 50% { box-shadow: 0 0 0 18px color-mix(in srgb, var(--oc) 0%, transparent); } }
1119
1119
  .gx-state.off .gx-orb { box-shadow: 0 0 0 2px var(--border-strong); }
1120
1120
  .gx-time { font-size: 13px; color: var(--muted); font-variant-numeric: tabular-nums; min-height: 1.3em; }
1121
- .gx-title { max-width: min(70ch, 92%); font-size: 13.5px; color: var(--faint); text-align: center; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-height: 1.3em; }
1121
+ /* Under the orb: the task in the spotlight, and the node in the middle of its chain (the step
1122
+ under way, by default), each with a mark saying how it stands. */
1123
+ .gx-lines { width: 100%; margin-top: -6px; display: flex; flex-direction: column; align-items: center; gap: 8px; }
1124
+ .gx-line { max-width: min(80ch, 92%); min-height: 20px; display: flex; align-items: center; justify-content: center; gap: 8px; }
1125
+ .gx-line .gx-tx { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1126
+ .gx-task { font-size: 14.5px; font-weight: 550; color: var(--muted); }
1127
+ .gx-step { font-size: 13px; color: var(--faint); }
1128
+ .gx-step small { flex: none; font-size: 10.5px; font-weight: 650; letter-spacing: 0.05em; text-transform: uppercase; color: var(--mc, var(--faint)); }
1129
+ .gx-mk { --mc: var(--faint); flex: none; box-sizing: border-box; width: 17px; height: 17px; border-radius: 50%; display: grid; place-items: center; color: var(--mc); }
1130
+ .gx-step .gx-mk { width: 15px; height: 15px; }
1131
+ .gx-mk svg { width: 66%; height: 66%; fill: none; stroke: currentColor; stroke-width: 3; stroke-linecap: round; stroke-linejoin: round; }
1132
+ .m-live, .m-idle { --mc: var(--accent); }
1133
+ .m-done { --mc: var(--done); }
1134
+ .m-bad { --mc: var(--bad); }
1135
+ .m-ask { --mc: var(--amber); }
1136
+ .m-wait, .m-pend, .m-you { --mc: var(--faint); }
1137
+ .gx-mk.m-done, .gx-mk.m-bad, .gx-mk.m-ask { background: color-mix(in srgb, var(--mc) 18%, transparent); box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--mc) 55%, transparent); }
1138
+ .gx-mk.m-ask svg { width: 80%; height: 80%; stroke-width: 2.2; }
1139
+ .gx-mk.m-you svg { width: 88%; height: 88%; stroke-width: 2; }
1140
+ .gx-mk.m-wait svg { width: 72%; height: 72%; }
1141
+ /* Running: a spinning ring. Under way but quiet: the ring, still and dashed. Not started: a plain ring. */
1142
+ .gx-mk.m-live { border: 2px solid color-mix(in srgb, var(--mc) 22%, transparent); border-top-color: var(--mc); animation: spin 0.9s linear infinite; }
1143
+ .gx-mk.m-idle { border: 1.6px dashed var(--mc); }
1144
+ .gx-mk.m-pend { border: 1.6px solid var(--mc); }
1122
1145
 
1146
+ /* Subagents still running: a tag on the orb's left, only while some run, naming the latest one
1147
+ ("+N" for the others); a click lists them all. */
1148
+ .gx-agents { --mc: var(--agent); position: absolute; right: calc(50% + 84px); top: 36px; width: max-content; max-width: min(300px, calc(50vw - 140px)); padding: 7px 13px 8px 10px;
1149
+ display: flex; align-items: center; gap: 9px; border: 1.5px solid color-mix(in srgb, var(--agent) 70%, transparent); border-radius: 14px;
1150
+ background: color-mix(in srgb, var(--agent) 9%, var(--surface)); color: var(--text); font: inherit; font-size: 13px; line-height: 1.35; text-align: left; cursor: pointer;
1151
+ transform-origin: right center; animation: gx-agents-in 0.45s cubic-bezier(0.2, 1.4, 0.4, 1); transition: background 0.2s, box-shadow 0.2s; }
1152
+ .gx-agents:hover, .gx-agents.open { background: color-mix(in srgb, var(--agent) 16%, var(--surface)); }
1153
+ .gx-agents.open { box-shadow: 0 0 0 3px color-mix(in srgb, var(--agent) 22%, transparent); }
1154
+ .gx-agents[hidden], .gx-agpop[hidden] { display: none; }
1155
+ .gx-ag-t { min-width: 0; display: flex; flex-direction: column; }
1156
+ .gx-agents small { font-size: 10.5px; font-weight: 650; letter-spacing: 0.05em; text-transform: uppercase; color: var(--agent); }
1157
+ .gx-ag-n { min-width: 0; display: flex; align-items: baseline; gap: 6px; }
1158
+ .gx-ag-t .gx-tx { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1159
+ .gx-ag-n b { flex: none; padding: 0 6px; border-radius: 999px; background: var(--agent); color: #fff; font-size: 11px; font-weight: 650; line-height: 17px; }
1160
+ @keyframes gx-agents-in { from { opacity: 0; transform: scale(0.6) translateX(12px); } }
1161
+ /* The list: under the tag, scrolling when long. */
1162
+ .gx-agpop { --mc: var(--agent); position: absolute; right: calc(50% + 84px); top: 92px; z-index: 5; width: 340px; max-width: calc(50vw - 110px); max-height: 280px; overflow-y: auto;
1163
+ padding: 5px; border: 1px solid color-mix(in srgb, var(--agent) 40%, var(--border)); border-radius: 12px; background: var(--surface);
1164
+ box-shadow: 0 16px 40px -16px rgb(0 0 0 / 0.45); animation: gx-pop-in 0.18s ease-out; }
1165
+ @keyframes gx-pop-in { from { opacity: 0; transform: translateY(-6px); } }
1166
+ .gx-ag-row { width: 100%; padding: 7px 9px; display: flex; align-items: center; gap: 10px; border: 0; border-radius: 8px; background: none; color: var(--text); font: inherit; font-size: 13px; text-align: left; cursor: pointer; }
1167
+ .gx-ag-row:hover, .gx-ag-row:focus-visible { background: color-mix(in srgb, var(--agent) 12%, var(--surface)); outline: none; }
1168
+ .gx-agents .gx-mk.m-live, .gx-agpop .gx-mk.m-live { --mc: var(--agent); }
1169
+ .gx-ag-row small { color: var(--faint); font-size: 11.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1123
1170
  /* The current task: its chain, larger, every node sized so the whole task fits the width. */
1124
1171
  /* The spotlight's chain: one row, one node in the middle, start and end always in view;
1125
1172
  placeChain() sets each node's place and opacity. */
@@ -1178,8 +1225,83 @@ pre .dh { color: var(--faint); font-weight: 600; }
1178
1225
  .gx-state.away .gx-orb { opacity: 0.8; }
1179
1226
  .gx-state.away .gx-orb:hover { opacity: 1; }
1180
1227
  @media (prefers-reduced-motion: reduce) {
1181
- .gx-orb::before, .gx-orb.swap svg, .gx-ask { animation: none !important; }
1228
+ .gx-orb::before, .gx-orb.swap svg, .gx-ask, .gx-agents, .gx-agpop, .gx-mk { animation: none !important; }
1182
1229
  .gx-track > .fn { transition: none; }
1183
1230
  .gx-track > .fn.gx-new .fn-s, .gx-track > .fn.gx-new .fn-l { animation: none; }
1184
1231
  }
1185
1232
 
1233
+
1234
+ /* ───────── session tabs: one per open session, above the top bar (from two tabs on) */
1235
+ .tabs {
1236
+ flex: none; display: flex; align-items: flex-end; gap: 2px; padding: 6px 12px 0; min-width: 0; overflow-x: auto; scrollbar-width: none;
1237
+ /* The line under the tabs is a shadow, so the tab on screen covers it and opens into the top bar. */
1238
+ background: var(--sunken); box-shadow: inset 0 -1px 0 var(--border); position: relative; z-index: 7;
1239
+ }
1240
+ .tabs::-webkit-scrollbar { display: none; }
1241
+ .tab {
1242
+ --c: var(--faint);
1243
+ flex: 0 1 220px; min-width: 110px; display: flex; align-items: center; gap: 7px; height: 32px; padding: 0 4px 0 11px;
1244
+ border: 1px solid transparent; border-bottom: 0; border-radius: 9px 9px 0 0; color: var(--muted); font-size: 12.5px; cursor: pointer;
1245
+ position: relative; transition: background 0.15s, color 0.15s;
1246
+ }
1247
+ .tab:hover { background: var(--hover); color: var(--text); }
1248
+ .tab.on { background: var(--surface); border-color: var(--border); color: var(--text); font-weight: 560; }
1249
+ .tab:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
1250
+ .tab .tt { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1251
+ .tab .tag { flex: none; min-width: 16px; height: 16px; padding: 0 4px; border-radius: 8px; font-size: 10.5px; font-weight: 650; line-height: 16px; text-align: center; color: var(--agent); background: var(--agent-bg); }
1252
+ .tab .tx { flex: none; width: 20px; height: 20px; padding: 3px; border: 0; border-radius: 5px; background: none; color: var(--faint); cursor: pointer; opacity: 0; }
1253
+ .tab:hover .tx, .tab.on .tx, .tab .tx:focus-visible { opacity: 1; }
1254
+ .tab .tx:hover { background: var(--border); color: var(--text); }
1255
+ .tab .tx svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; }
1256
+ .tab-add { flex: none; align-self: center; width: 26px; height: 26px; margin: 0 0 3px 4px; padding: 5px; border: 0; border-radius: 7px; background: none; color: var(--muted); cursor: pointer; }
1257
+ .tab-add:hover { background: var(--hover); color: var(--text); }
1258
+ .tab-add svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; }
1259
+
1260
+ /* The state dot, in the orb's colours: working (pulsing), your turn, in trouble, finished. */
1261
+ .tdot { flex: none; width: 8px; height: 8px; border-radius: 50%; background: var(--c); }
1262
+ .tab:has(.s-work) { --c: var(--accent); }
1263
+ .tab:has(.s-you) { --c: var(--warn); }
1264
+ .tab:has(.s-bad) { --c: var(--bad); }
1265
+ .tab:has(.s-done), .tab:has(.s-rest) { --c: var(--done); }
1266
+ .tdot.s-rest { opacity: 0.55; }
1267
+ .tdot.s-quiet, .tdot.s-paused { background: none; box-shadow: inset 0 0 0 1.5px var(--faint); }
1268
+ .tdot.s-work { animation: beat 1.6s ease-out infinite; }
1269
+
1270
+ /* A tab that wants you: it flashes a few times, then glows until you open it. */
1271
+ .tab.attn:not(.on) { color: var(--text); background: color-mix(in srgb, var(--c) 14%, var(--sunken)); border-color: color-mix(in srgb, var(--c) 45%, transparent); }
1272
+ .tab.attn:not(.on)::after { content: ''; position: absolute; left: 10px; right: 10px; bottom: 0; height: 2px; border-radius: 2px; background: var(--c); }
1273
+ .tab.flash:not(.on) { animation: tab-flash 0.8s ease-in-out 4; }
1274
+ @keyframes tab-flash { 50% { background: color-mix(in srgb, var(--c) 42%, var(--sunken)); box-shadow: 0 0 14px color-mix(in srgb, var(--c) 55%, transparent); } }
1275
+
1276
+ /* The session list: what's already in a tab, and a "new tab" button on each row. */
1277
+ .plist .pin-tab { flex: none; font-size: 11px; font-weight: 600; color: var(--accent); background: var(--accent-bg); padding: 1px 7px; border-radius: 99px; white-space: nowrap; }
1278
+ .plist .pnew { flex: none; height: 24px; margin: -4px -2px -4px 0; padding: 0 9px; border: 1px solid var(--border-strong); border-radius: 6px; background: var(--surface); color: var(--muted); font-size: 12px; font-weight: 550; white-space: nowrap; cursor: pointer; opacity: 0; transition: opacity 0.15s, background 0.15s, color 0.15s; }
1279
+ .plist li:hover .pnew, .plist .pnew:focus-visible { opacity: 1; }
1280
+ .plist .pnew:hover { background: var(--accent-bg); border-color: color-mix(in srgb, var(--accent) 45%, transparent); color: var(--accent); }
1281
+ .picker-hint { flex: none; padding: 0 18px 8px; color: var(--faint); font-size: 11.5px; }
1282
+ .picker-hint:empty { display: none; }
1283
+
1284
+ /* ───────── sound alerts (the bell next to keep awake) */
1285
+ .snd { color: var(--faint); }
1286
+ .snd svg { width: 19px; height: 19px; stroke-width: 1.7; }
1287
+ .snd .waves { opacity: 0; transition: opacity 0.2s; }
1288
+ .snd .mute { opacity: 0; }
1289
+ .snd[data-snd='off'] .mute { opacity: 1; }
1290
+ .snd[data-snd='on'] { color: var(--accent); }
1291
+ .snd[data-snd='on'] .waves { opacity: 1; }
1292
+ .snd[data-snd='wait'] { color: var(--warn); }
1293
+ .snd[data-snd='err'] { color: var(--bad); }
1294
+ #soundPop { width: 390px; }
1295
+ .sn-ev { display: flex; align-items: flex-start; gap: 8px; margin: 0 0 10px 2px; }
1296
+ .sn-l { flex: 1; min-width: 0; font-size: 12.5px; }
1297
+ .sn-l b { font-weight: 560; }
1298
+ .sn-l small { display: block; color: var(--muted); font-size: 11.5px; margin-top: 2px; line-height: 1.35; }
1299
+ .sn-ev select { flex: none; width: 118px; height: 24px; margin-top: -2px; padding: 0 6px; border: 1px solid var(--border); border-radius: 6px; background: var(--surface); font-size: 12px; cursor: pointer; }
1300
+ .sn-ev .tbtn:disabled { opacity: 0.4; cursor: default; }
1301
+ .sn-ev .tbtn { flex: none; height: 24px; padding: 0 9px; font-size: 12px; margin-top: -2px; }
1302
+ .sn-away { padding-top: 10px; border-top: 1px solid var(--border); }
1303
+ .sn-vol { display: flex; align-items: center; gap: 10px; margin: 0 0 10px 2px; font-size: 12.5px; color: var(--muted); }
1304
+ .sn-vol input { flex: 1; accent-color: var(--accent); }
1305
+ .wk-st.st-on { color: var(--text); }
1306
+ .wk-st.st-on i { background: var(--accent); }
1307
+ .wk-st.st-wait i { background: var(--warn); }