@nanhara/hara 0.99.0 → 0.99.1

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.
@@ -37,8 +37,9 @@ export function footerCwd(abs, home = process.env.HOME ?? "", maxLen = 28) {
37
37
  }
38
38
  /** Compose the single dim footer line rendered below the box. Pure so the ordering/spacing (and the
39
39
  * optional route segment) can be pinned without rendering React. Shape (codex-style status line):
40
- * `<model> · <approval>[ · <route>] · <cwd> · ↑<in> ↓<out> · ctx <pct>%`. `ctx N%` is always present
41
- * (from 0 on) so the field never pops in mid-session and shifts the layout. */
40
+ * `<model> · <approval>[ · <route>] · <cwd> · ↑<in> ↓<out> · ctx <pct>%`. The session name is NOT here —
41
+ * it rides the input box's top-right border (see TopBorder). `ctx N%` is always present (from 0 on) so
42
+ * the field never pops in mid-session and shifts the layout. */
42
43
  export function footerLine(model, s, cwdShort, route) {
43
44
  const routeSeg = route ? ` · ${route}` : "";
44
45
  return ` ${model} · ${s.approval}${routeSeg} · ${cwdShort} · ↑${tok(s.input)} ↓${tok(s.output)} · ctx ${s.ctxPct}%`;
@@ -48,6 +49,16 @@ export function footerLine(model, s, cwdShort, route) {
48
49
  const Footer = memo(function Footer({ model, s, cwdShort, route }) {
49
50
  return _jsx(Text, { dimColor: true, children: footerLine(model, s, cwdShort, route) });
50
51
  });
52
+ // The rounded TOP edge of the input box, carrying the session name in the right corner (it "rides"
53
+ // the border — codex-style titled panel, and where hara has always shown it). Drawn by hand because
54
+ // ink's <Box borderStyle> has no title slot; the box below it renders with `borderTop={false}` and a
55
+ // fixed `width`, so this line supplies the top with its two corners and everything lines up exactly.
56
+ // Layout (total = width): `╭` + left dashes + ` ● <name> ` + `─╮`. `●` (U+25CF) is a hard 1-cell glyph
57
+ // (unlike the emoji-presentation `⏺`, which some terminals render 2 cells wide and would skew the corner).
58
+ const TopBorder = memo(function TopBorder({ name, width }) {
59
+ const left = Math.max(2, width - name.length - 7); // ╭(1)+ " "(1)+●(1)+" name "(len+2)+"─╮"(2)
60
+ return (_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "╭" + "─".repeat(left) + " " }), _jsx(Text, { color: "cyan", children: "\u25CF" }), _jsx(Text, { bold: true, children: ` ${name} ` }), _jsx(Text, { dimColor: true, children: "─╮" })] }));
61
+ });
51
62
  const MODE_DESC = {
52
63
  suggest: "confirms edits & commands",
53
64
  "auto-edit": "auto-applies edits · asks before commands",
@@ -369,9 +380,11 @@ export function InputBox({ status, cwd, model, route, width, onSubmit, onClipboa
369
380
  }, { isActive });
370
381
  const gutter = vim && mode === "normal" ? "◆ " : "› ";
371
382
  const gutterColor = vim ? (mode === "normal" ? "yellow" : "green") : "cyan";
372
- // The prompt box borders subtract 2 cells (1 each side) from the usable text width; InputLine's
373
- // deterministic wrap needs the INNER width so the cursor/continuation gutter stay exact.
374
- const innerW = Math.max(1, w - 2);
383
+ // The box borders (1 each side) + paddingX (1 each side) subtract 4 cells; InputLine's deterministic
384
+ // wrap needs the INNER width so the cursor/continuation gutter stay exact. The box is a fixed `width={w}`
385
+ // with `borderTop={false}` so the hand-drawn TopBorder (with the session title) supplies the top edge
386
+ // + corners and everything aligns column-for-column.
387
+ const innerW = Math.max(1, w - 4);
375
388
  const cwdShort = footerCwd(cwd);
376
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { borderStyle: "round", borderColor: "gray", borderDimColor: true, paddingX: 1, children: _jsx(InputLine, { value: value, cursor: cursor, width: innerW, gutter: gutter, gutterColor: gutterColor, placeholder: placeholder }) }), vim ? _jsx(Text, { dimColor: true, children: mode === "normal" ? " -- NORMAL -- i/a insert · h l 0 $ w b e move · x dd D cw p edit" : " -- INSERT -- Esc → normal" }) : null, _jsx(Footer, { model: model, s: status, cwdShort: cwdShort, route: route }), working ? _jsx(Text, { dimColor: true, children: ` ⌨ working — Enter queues your message${queued ? ` · ${queued} queued` : ""} · Esc interrupts` }) : null, popupOpen ? _jsx(MentionPopup, { items: candidates, selected: selIdx, query: mention.query }) : null, _jsx(ModeBar, { approval: status.approval })] }));
389
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(TopBorder, { name: status.sessionName || "session", width: w }), _jsx(Box, { borderStyle: "round", borderTop: false, borderColor: "gray", borderDimColor: true, paddingX: 1, width: w, children: _jsx(InputLine, { value: value, cursor: cursor, width: innerW, gutter: gutter, gutterColor: gutterColor, placeholder: placeholder }) }), vim ? _jsx(Text, { dimColor: true, children: mode === "normal" ? " -- NORMAL -- i/a insert · h l 0 $ w b e move · x dd D cw p edit" : " -- INSERT -- Esc → normal" }) : null, _jsx(Footer, { model: model, s: status, cwdShort: cwdShort, route: route }), working ? _jsx(Text, { dimColor: true, children: ` ⌨ working — Enter queues your message${queued ? ` · ${queued} queued` : ""} · Esc interrupts` }) : null, popupOpen ? _jsx(MentionPopup, { items: candidates, selected: selIdx, query: mention.query }) : null, _jsx(ModeBar, { approval: status.approval })] }));
377
390
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nanhara/hara",
3
- "version": "0.99.0",
3
+ "version": "0.99.1",
4
4
  "description": "hara — a coding agent CLI that runs like an engineering org.",
5
5
  "bin": {
6
6
  "hara": "dist/index.js"