jaitus 0.3.21 → 0.3.22

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jaitus",
3
- "version": "0.3.21",
3
+ "version": "0.3.22",
4
4
  "description": "Jaitus — a coding agent in your terminal.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -16,7 +16,7 @@
16
16
  "node": ">=20"
17
17
  },
18
18
  "scripts": {
19
- "test": "node tests/source-sanity.js && node tests/react-build.js && node tests/stalls.js && node tests/clipboard-write.js && node tests/rewind-images.js && node tests/rules.js && node tests/run.js && node tests/commands.js && node tests/login.js && node tests/session.js && node tests/openUrl.js && node tests/ink-tui.js && node tests/tui-render.js && node tests/mouse-e2e.js && node tests/environment.js && node tests/update.js && node tests/service-notices.js && node tests/continuation.js && node tests/steps.js && node tests/files.js && node tests/processes.js && node tests/security.js && node tests/context-budget.js && node tests/context-trim.js && node tests/compaction.js && node tests/copy-clean.js && node tests/parallel-tools.js && node tests/plan-percent.js && node tests/project-identity.js",
19
+ "test": "node tests/source-sanity.js && node tests/react-build.js && node tests/stalls.js && node tests/clipboard-write.js && node tests/rewind-images.js && node tests/rules.js && node tests/run.js && node tests/commands.js && node tests/login.js && node tests/session.js && node tests/openUrl.js && node tests/ink-tui.js && node tests/tui-render.js && node tests/mouse-e2e.js && node tests/environment.js && node tests/update.js && node tests/service-notices.js && node tests/continuation.js && node tests/steps.js && node tests/files.js && node tests/processes.js && node tests/security.js && node tests/context-budget.js && node tests/context-trim.js && node tests/compaction.js && node tests/copy-clean.js && node tests/parallel-tools.js && node tests/plan-percent.js && node tests/orb.js && node tests/project-identity.js",
20
20
  "preview": "node scripts/tui-preview.js",
21
21
  "prepublishOnly": "npm test"
22
22
  },
package/src/tui/orb.js ADDED
@@ -0,0 +1,41 @@
1
+ // ─── The orb ────────────────────────────────────────────────────────────────────
2
+ //
3
+ // The power source the website draws its energy from, in two terminal cells: a ring of
4
+ // braille dots spinning round a core that crackles, flaring white every couple of
5
+ // seconds. It replaces the one-cell spinner wherever work is running, so the terminal
6
+ // shows the same thing the site does — the work is being powered by the orb.
7
+ //
8
+ // Two cells are a 4×4 grid of dots. The ring is the eight dots on the edge; a two-dot
9
+ // gap travels round it, which is what reads as spin. The four centre dots are the core.
10
+ // Pure function of the tick, so it costs nothing beyond the shared animation timer, and
11
+ // frame 0 is a sensible still for reduced motion.
12
+
13
+ // Braille bit for column c (0..1) and row r (0..3) inside one cell.
14
+ const BIT = [[0x01, 0x02, 0x04, 0x40], [0x08, 0x10, 0x20, 0x80]];
15
+
16
+ // The ring, clockwise from the top: [column 0..3, row 0..3] on the 4×4 grid.
17
+ const RING = [[1, 0], [2, 0], [3, 1], [3, 2], [2, 3], [1, 3], [0, 2], [0, 1]];
18
+ const CORE = [[1, 1], [2, 1], [1, 2], [2, 2]];
19
+
20
+ function cells(dots) {
21
+ const bits = [0, 0];
22
+ for (const [x, y] of dots) bits[x >> 1] |= BIT[x & 1][y];
23
+ return String.fromCharCode(0x2800 + bits[0], 0x2800 + bits[1]);
24
+ }
25
+
26
+ /**
27
+ * The orb at a given tick. `level` is 0 (steady), 1 (the core crackling) or 2 (a surge:
28
+ * every dot lit) so the caller can colour it — brand, lighter, near-white.
29
+ */
30
+ export function orbFrame(tick) {
31
+ const n = Math.max(0, Math.floor(tick));
32
+ if (n % 30 < 2 && n >= 30) return { text: cells([...RING, ...CORE]), level: 2 };
33
+ const gap = n % RING.length;
34
+ const ring = RING.filter((_, i) => i !== gap && i !== (gap + 1) % RING.length);
35
+ // The core crackles on an irregular beat — a fixed pattern of primes rather than
36
+ // random, so a frame is reproducible in tests and previews.
37
+ const crackle = n % 11 === 3 || n % 11 === 4 || n % 7 === 0;
38
+ return { text: cells(crackle ? [...ring, ...CORE] : ring), level: crackle ? 1 : 0 };
39
+ }
40
+
41
+ export const ORB_STILL = orbFrame(0).text;
@@ -13,6 +13,7 @@
13
13
  import React from 'react';
14
14
  import { Box, Text, useAnimation } from 'ink';
15
15
  import { palette, mix, ramp } from './theme.js';
16
+ import { orbFrame } from './orb.js';
16
17
 
17
18
  const h = React.createElement;
18
19
 
@@ -59,6 +60,19 @@ export function Spinner({ kind = 'braille', color, interval = 80 }) {
59
60
  return h(Text, { color: color || p.brand }, active ? frames[frame % frames.length] : frames[0]);
60
61
  }
61
62
 
63
+ /**
64
+ * The orb (tui/orb.js) as the working indicator: two cells, a spinning ring round a
65
+ * crackling core that surges white. Same shared timer as every other widget.
66
+ */
67
+ export function OrbSpinner({ interval = 90 }) {
68
+ const p = palette();
69
+ const active = motionOn();
70
+ const { frame } = useAnimation({ interval, isActive: active });
71
+ const f = orbFrame(active ? frame : 0);
72
+ const color = f.level === 2 ? mix(p.brand, p.text, 0.75) : f.level === 1 ? p.brandSoft : p.brand;
73
+ return h(Text, { color }, f.text);
74
+ }
75
+
62
76
  /**
63
77
  * Text with a highlight travelling through it.
64
78
  *
package/src/tui-ink.js CHANGED
@@ -28,7 +28,7 @@ import { Box, Spacer, Text, render, useAnimation, useApp, useCursor, useInput, u
28
28
  import { palette, resetPalette, mix, ramp } from './tui/theme.js';
29
29
  import { makeEntry, rowsFor, invalidate, expandable } from './tui/entries.js';
30
30
  import { plain, stringWidth, truncateRow, wrapRow, rowText, inverseRange, copyText, softJoin, isDecorationRow } from './tui/ansi.js';
31
- import { Spinner, Shimmer, GradientText, FlowingRule, Meter, Elapsed, CursorRow, Rows, Row, Reveal, motionOn, publishSettings } from './tui/widgets.js';
31
+ import { Spinner, OrbSpinner, Shimmer, GradientText, FlowingRule, Meter, Elapsed, CursorRow, Rows, Row, Reveal, motionOn, publishSettings } from './tui/widgets.js';
32
32
  import { wordRows, wordSize } from './tui/wordmark.js';
33
33
  import { enableMouse, disableMouse, parseMouse, isMouse, isMouseFragment, createHitMap } from './tui/mouse.js';
34
34
  import { openUrl } from './open.js';
@@ -590,7 +590,7 @@ function WorkBar({ state, width }) {
590
590
  const label = plain(state.status) || 'Working';
591
591
  const narrow = width < 70;
592
592
  return h(Box, { flexDirection: 'row', paddingX: 1 },
593
- h(Spinner, { kind: ascii() ? 'ascii' : 'braille' }),
593
+ ascii() ? h(Spinner, { kind: 'ascii' }) : h(OrbSpinner),
594
594
  h(Text, null, ' '),
595
595
  h(Shimmer, { from: p.brand, to: p.brandSoft }, truncatePlain(label, Math.max(12, width - (narrow ? 22 : 46)))),
596
596
  h(Spacer),
package/src/ui.js CHANGED
@@ -18,6 +18,7 @@ import path from 'node:path';
18
18
  import os from 'node:os';
19
19
  import { loadSettings, HOME } from './config.js';
20
20
  import { terminalBackground } from './terminal.js';
21
+ import { orbFrame } from './tui/orb.js';
21
22
 
22
23
  // Read without re-entering config's write paths; failures mean defaults.
23
24
  function loadSettingsFile() { return loadSettings(); }
@@ -266,9 +267,8 @@ export function spinner(text) {
266
267
  out(`${c.dim(glyph('…', '...'))} ${text}`);
267
268
  return { stop: () => {}, update: () => {} };
268
269
  }
269
- const frames = unicode()
270
- ? ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
271
- : ['|', '/', '-', '\\'];
270
+ // The orb (tui/orb.js) where the terminal can draw braille; a plain bar where it can't.
271
+ const frames = unicode() ? null : ['|', '/', '-', '\\'];
272
272
  let i = 0;
273
273
  let label = text;
274
274
  // stop() is called more than once per spinner: runAgent stops it when the first
@@ -285,7 +285,11 @@ export function spinner(text) {
285
285
  const tick = () => {
286
286
  if (stopped) return;
287
287
  const n = i++;
288
- write(`\r\x1b[2K${c.brand(frames[n % frames.length])} ${sweep(label, n)}`);
288
+ const glyphNow = frames ? c.brand(frames[n % frames.length]) : (() => {
289
+ const f = orbFrame(n);
290
+ return f.level ? c.shimmer(f.text) : c.brand(f.text);
291
+ })();
292
+ write(`\r\x1b[2K${glyphNow} ${sweep(label, n)}`);
289
293
  };
290
294
  tick();
291
295
  const timer = setInterval(tick, 80);