@lawaty/lacode 1.3.0-beta.4 → 1.3.0-beta.5
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/CHANGELOG.md +17 -0
- package/dist/tui.js +61 -43
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,23 @@ All notable changes to LaCode are recorded here. The format follows
|
|
|
7
7
|
Versions come from git release commits; the npm version is bumped in the same
|
|
8
8
|
commit as the changelog entry it describes.
|
|
9
9
|
|
|
10
|
+
## [1.3.0-beta.5] — the timeline sidebar renders and updates
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **The timeline panel was never shown in the TUI.** Its slot registration sat
|
|
15
|
+
at the end of the voice plugin's setup, after the recorder, daemon and keymap
|
|
16
|
+
work, so an early return or a failure anywhere in that path skipped it. It is
|
|
17
|
+
now registered first and independently of voice, including when voice is
|
|
18
|
+
disabled with `enabled: false`. A registration failure is logged instead of
|
|
19
|
+
being swallowed.
|
|
20
|
+
- **The panel could not update once rendered.** Its rows were driven by
|
|
21
|
+
`solid-js` signals, but the TUI gives the plugin a different Solid instance
|
|
22
|
+
from the renderer's, so signal updates never reached the screen: the panel
|
|
23
|
+
stayed on "No timeline yet" even while the JSONL file grew. The rows are now
|
|
24
|
+
static `text` renderables whose `content` is written on a one-second timer
|
|
25
|
+
through the renderable itself, which is independent of any Solid instance.
|
|
26
|
+
|
|
10
27
|
## [1.3.0-beta.4] — session events count as life, and the timeline builds on idle
|
|
11
28
|
|
|
12
29
|
### Fixed
|
package/dist/tui.js
CHANGED
|
@@ -822,56 +822,72 @@ function createVoiceClient(options, deps) {
|
|
|
822
822
|
}
|
|
823
823
|
|
|
824
824
|
// src/tui/timeline.tsx
|
|
825
|
-
import { createSignal, createEffect, onCleanup, For } from "solid-js";
|
|
826
825
|
import { readFileSync as readFileSync3 } from "node:fs";
|
|
827
826
|
import { homedir as homedir2 } from "node:os";
|
|
828
827
|
import { join as join3, resolve } from "node:path";
|
|
829
828
|
import { jsx, jsxs } from "@opentui/solid/jsx-runtime";
|
|
830
829
|
var registered = false;
|
|
830
|
+
var ENTRY_LINES = 14;
|
|
831
|
+
var readEntries = (id) => {
|
|
832
|
+
if (typeof id !== "string" || !/^[A-Za-z0-9_.-]+$/.test(id)) return [];
|
|
833
|
+
const rawDir = process.env.LACODE_TIMELINE_DIR;
|
|
834
|
+
const dir = rawDir ? resolve(rawDir) : join3(homedir2(), ".local/share/opencode/lacode/timeline");
|
|
835
|
+
try {
|
|
836
|
+
const raw = readFileSync3(join3(dir, id + ".jsonl"), "utf8");
|
|
837
|
+
const parsed = [];
|
|
838
|
+
for (const line of raw.split("\n")) {
|
|
839
|
+
const trimmed = line.trim();
|
|
840
|
+
if (!trimmed) continue;
|
|
841
|
+
try {
|
|
842
|
+
const obj = JSON.parse(trimmed);
|
|
843
|
+
if (obj && typeof obj.ts === "number" && typeof obj.text === "string") parsed.push(obj.text);
|
|
844
|
+
} catch {
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
return parsed.slice(-ENTRY_LINES).reverse();
|
|
848
|
+
} catch {
|
|
849
|
+
return [];
|
|
850
|
+
}
|
|
851
|
+
};
|
|
831
852
|
var tui = async (api) => {
|
|
832
853
|
if (registered) return;
|
|
833
854
|
registered = true;
|
|
834
855
|
api.slots.register({
|
|
835
856
|
order: 150,
|
|
836
857
|
slots: {
|
|
858
|
+
// The rows are static and their text is written through the renderable's
|
|
859
|
+
// `content` setter. Solid signals are deliberately NOT used here: the
|
|
860
|
+
// plugin's `solid-js` is a different instance from the TUI renderer's, so
|
|
861
|
+
// signal updates never reach the screen. Plain renderables always do.
|
|
837
862
|
sidebar_content(ctx, props) {
|
|
838
|
-
const
|
|
839
|
-
const
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
const raw = readFileSync3(file, "utf8");
|
|
849
|
-
const parsed = [];
|
|
850
|
-
for (const line of raw.split("\n")) {
|
|
851
|
-
const trimmed = line.trim();
|
|
852
|
-
if (!trimmed) continue;
|
|
853
|
-
try {
|
|
854
|
-
const obj = JSON.parse(trimmed);
|
|
855
|
-
if (obj && typeof obj.ts === "number" && typeof obj.text === "string") {
|
|
856
|
-
parsed.push(obj.text);
|
|
857
|
-
}
|
|
858
|
-
} catch {
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
setEntries(parsed.slice(-14).reverse());
|
|
862
|
-
} catch {
|
|
863
|
-
setEntries([]);
|
|
863
|
+
const rows = [];
|
|
864
|
+
const muted = ctx.theme.current.textMuted;
|
|
865
|
+
const paint = () => {
|
|
866
|
+
const entries = readEntries(props.session_id);
|
|
867
|
+
for (let i = 0; i < ENTRY_LINES; i++) {
|
|
868
|
+
const row = rows[i];
|
|
869
|
+
if (!row) continue;
|
|
870
|
+
const text = entries[i];
|
|
871
|
+
row.content = text ? `\u2022 ${text}` : i === 0 && entries.length === 0 ? "No timeline yet" : "";
|
|
872
|
+
row.requestRender?.();
|
|
864
873
|
}
|
|
865
874
|
};
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
const timer = setInterval(() => read(id), 1e3);
|
|
870
|
-
onCleanup(() => clearInterval(timer));
|
|
871
|
-
});
|
|
875
|
+
const timer = setInterval(paint, 1e3);
|
|
876
|
+
timer.unref?.();
|
|
877
|
+
api.lifecycle.onDispose(() => clearInterval(timer));
|
|
872
878
|
return /* @__PURE__ */ jsxs("box", { flexDirection: "column", children: [
|
|
873
|
-
/* @__PURE__ */ jsx("text", { fg:
|
|
874
|
-
|
|
879
|
+
/* @__PURE__ */ jsx("text", { fg: muted, children: "Timeline" }),
|
|
880
|
+
Array.from({ length: ENTRY_LINES }, (_, index) => /* @__PURE__ */ jsx(
|
|
881
|
+
"text",
|
|
882
|
+
{
|
|
883
|
+
fg: index === 0 ? muted : void 0,
|
|
884
|
+
ref: (node) => {
|
|
885
|
+
rows[index] = node;
|
|
886
|
+
if (index === ENTRY_LINES - 1) paint();
|
|
887
|
+
},
|
|
888
|
+
children: ""
|
|
889
|
+
}
|
|
890
|
+
))
|
|
875
891
|
] });
|
|
876
892
|
}
|
|
877
893
|
}
|
|
@@ -1112,6 +1128,16 @@ async function answerWithText(api, opts, text, request) {
|
|
|
1112
1128
|
toast(api, { variant: "success", message: labels });
|
|
1113
1129
|
}
|
|
1114
1130
|
var tui2 = async (api, options) => {
|
|
1131
|
+
const timelineOpts = options ?? {};
|
|
1132
|
+
const timelineOff = timelineOpts.timeline === false || typeof timelineOpts.timeline === "object" && timelineOpts.timeline !== null && timelineOpts.timeline.enabled === false;
|
|
1133
|
+
if (!timelineOff) {
|
|
1134
|
+
try {
|
|
1135
|
+
await timeline_default.tui(api, options);
|
|
1136
|
+
} catch (error) {
|
|
1137
|
+
void api.client?.app?.log?.({ body: { service: "timeline", level: "warn", message: `timeline sidebar registration failed: ${String(error)}` } })?.catch?.(() => {
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1115
1141
|
const opts = resolveOptions(options);
|
|
1116
1142
|
if (!opts.enabled) return;
|
|
1117
1143
|
daemonConnected = false;
|
|
@@ -1333,14 +1359,6 @@ var tui2 = async (api, options) => {
|
|
|
1333
1359
|
bindings.push({ key: opts.answerKey, cmd: "lacode.voice.answer", desc: "Voice answer" });
|
|
1334
1360
|
}
|
|
1335
1361
|
api.keymap.registerLayer({ mode: "base", commands, bindings });
|
|
1336
|
-
const timelineOpts = options ?? {};
|
|
1337
|
-
const timelineOff = timelineOpts.timeline === false || typeof timelineOpts.timeline === "object" && timelineOpts.timeline !== null && timelineOpts.timeline.enabled === false;
|
|
1338
|
-
if (!timelineOff) {
|
|
1339
|
-
try {
|
|
1340
|
-
await timeline_default.tui(api, options);
|
|
1341
|
-
} catch {
|
|
1342
|
-
}
|
|
1343
|
-
}
|
|
1344
1362
|
};
|
|
1345
1363
|
var mod = {
|
|
1346
1364
|
id: "lacode.voice",
|
package/package.json
CHANGED