beatrina 0.9.83 → 1.0.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/app/Beatrina.app/Contents/Info.plist +86 -0
- package/app/Beatrina.app/Contents/MacOS/beatrina +0 -0
- package/app/Beatrina.app/Contents/Resources/icon.icns +0 -0
- package/app/beatrina.png +0 -0
- package/app/linux-arm64/beatrina +0 -0
- package/app/win-x64/Beatrina-setup.exe +0 -0
- package/beatrina_V1.0.0.html +1906 -0
- package/beatrina_V1.0.0.html.inputs.json +1 -0
- package/bin/beatrina.mjs +200 -12
- package/bin/cli.mjs +8 -3
- package/bin/finder.mjs +1 -1
- package/bin/shortcut.mjs +15 -15
- package/build-info.json +1 -1
- package/host/engine-node-job.mjs +35 -0
- package/host/engine-pool.mjs +153 -26
- package/host/engine-r.mjs +8 -1
- package/host/jobs/document-child.mjs +271 -0
- package/host/latex-compile.mjs +673 -0
- package/host/main.mjs +58 -18
- package/host/planes/analyze.mjs +18 -2
- package/host/planes/jobs.mjs +38 -5
- package/host/planes/sessions.mjs +5 -1
- package/host/pwa/icon-192.png +0 -0
- package/host/pwa/icon-512.png +0 -0
- package/host/quarto.mjs +118 -0
- package/host/server.mjs +83 -2
- package/host/worker-plane.mjs +7 -1
- package/kernel/check/acceptance.mjs +43 -12
- package/kernel/check/knit-file.mjs +46 -14
- package/kernel/check/session.mjs +19 -4
- package/kernel/kernel-version +1 -1
- package/lib/engine-labels.js +7 -1
- package/menu/Beatrina Menu.app/Contents/Info.plist +2 -2
- package/menu/Beatrina Menu.app/Contents/MacOS/Beatrina Menu +0 -0
- package/package.json +4 -3
- package/beatrina_V0.9.83.html +0 -1903
- package/beatrina_V0.9.83.html.inputs.json +0 -1
|
@@ -12,6 +12,10 @@
|
|
|
12
12
|
// kept · readline() · file ops and completion while R is busy · Force Stop ·
|
|
13
13
|
// the terminal · Python and JavaScript chunks when those engines exist.
|
|
14
14
|
//
|
|
15
|
+
// WITH NO R (plans/getting_rid_of_R.md Stage 2) the host is still judged —
|
|
16
|
+
// startup, the document (save, open, list), the terminal, the other engines —
|
|
17
|
+
// and every R area is reported SKIP with the reason, never counted as a pass.
|
|
18
|
+
//
|
|
15
19
|
// Usage
|
|
16
20
|
// node tools/beatrina/acceptance.mjs # this checkout's host
|
|
17
21
|
// node tools/beatrina/acceptance.mjs --installed # the installed beatrina package's host
|
|
@@ -61,17 +65,44 @@ if (!session.announced) {
|
|
|
61
65
|
record("startup", "the supervisor announces a URL", "fail", session.log().split("\n").slice(-6).join(" | "));
|
|
62
66
|
await shutdown(1);
|
|
63
67
|
}
|
|
64
|
-
const { frames, until, ready, page } = session;
|
|
65
|
-
if (!ready) { record("startup", "R announces ready", "fail", session.log().slice(-400)); await shutdown(1); }
|
|
66
|
-
|
|
68
|
+
const { frames, until, ready, page, hostReady, noR } = session;
|
|
69
|
+
if (!ready && !noR) { record("startup", "R announces ready", "fail", session.log().slice(-400)); await shutdown(1); }
|
|
70
|
+
if (noR) {
|
|
71
|
+
record("startup", "the host is ready with no R (host-ready)", hostReady ? "pass" : "fail", "", session.startupMs);
|
|
72
|
+
record("startup", "R", "skip", `R is not available: ${noR}. Every R area below is skipped, not passed.`);
|
|
73
|
+
} else {
|
|
74
|
+
record("startup", `R ready (${ready.r || "?"}, worker mode ${ready.mode || "?"})`, "pass", "", session.startupMs, { r: ready.r, mode: ready.mode });
|
|
75
|
+
}
|
|
76
|
+
/** An R area runs only when R does; otherwise it is ONE skip line with the reason. */
|
|
77
|
+
const wantR = (area) => {
|
|
78
|
+
if (!want(area)) return false;
|
|
79
|
+
if (noR) { record(area, `${area} (needs R)`, "skip", noR); return false; }
|
|
80
|
+
return true;
|
|
81
|
+
};
|
|
67
82
|
|
|
68
83
|
let seq = 0;
|
|
69
84
|
const text = (id) => session.text(id);
|
|
70
85
|
const run = (source, opts = {}) => session.run(source, opts);
|
|
71
|
-
const
|
|
86
|
+
const announced = ready || hostReady;
|
|
87
|
+
const engines = Array.isArray(announced.engines) ? announced.engines : ["r"];
|
|
88
|
+
|
|
89
|
+
// ── the document: what a person does with no engine at all ──────────────────
|
|
90
|
+
if (want("document")) {
|
|
91
|
+
const doc = path.join(scratch, "chapter.qmd");
|
|
92
|
+
const body = "---\ntitle: Acceptance\n---\n\n# Method\n\nWords, saved by the host [@key].\n";
|
|
93
|
+
const w = await session.ask("writefile", { path: doc, text: body });
|
|
94
|
+
record("document", "Save writes a .qmd", w.reply && !w.reply.error && fs.readFileSync(doc, "utf8") === body ? "pass" : "fail",
|
|
95
|
+
w.reply ? (w.reply.error || "") : "no reply", w.ms);
|
|
96
|
+
const rd = await session.ask("readfile", { path: doc });
|
|
97
|
+
record("document", "Open reads it back, byte for byte", rd.reply && rd.reply.text === body ? "pass" : "fail",
|
|
98
|
+
rd.reply ? (rd.reply.error || "") : "no reply", rd.ms);
|
|
99
|
+
const ls = await session.ask("files", { path: scratch });
|
|
100
|
+
record("document", "the file browser lists it", ls.reply && !ls.reply.error && JSON.stringify(ls.reply).includes("chapter.qmd") ? "pass" : "fail",
|
|
101
|
+
ls.reply ? (ls.reply.error || "") : "no reply", ls.ms);
|
|
102
|
+
}
|
|
72
103
|
|
|
73
104
|
// ── run ──────────────────────────────────────────────────────────────────────
|
|
74
|
-
if (
|
|
105
|
+
if (wantR("run")) {
|
|
75
106
|
const r = await run('keep <- 42; cat("keep=", keep, "\\n")');
|
|
76
107
|
record("run", "a chunk runs and prints", r.done?.status === "ok" && /keep= 42/.test(r.out()) ? "pass" : "fail", r.out().trim(), r.ms);
|
|
77
108
|
const warm = [];
|
|
@@ -109,7 +140,7 @@ async function stopCase(label, source, { engine, probe, expect, after = 900, kee
|
|
|
109
140
|
record("stop", `${label}: interrupted with the session kept, ${REPEAT - misses.length}/${REPEAT}`, misses.length ? "fail" : "pass",
|
|
110
141
|
summary, lat.length ? median(lat) : null, { series: lat, misses: misses.length, repeat: REPEAT });
|
|
111
142
|
}
|
|
112
|
-
if (
|
|
143
|
+
if (wantR("stop")) {
|
|
113
144
|
const probe = 'cat("keep=", exists("keep") && keep == 42, "\\n")';
|
|
114
145
|
await run("keep <- 42");
|
|
115
146
|
const keep = "keep <- 42";
|
|
@@ -119,7 +150,7 @@ if (want("stop")) {
|
|
|
119
150
|
}
|
|
120
151
|
|
|
121
152
|
// ── readline ─────────────────────────────────────────────────────────────────
|
|
122
|
-
if (
|
|
153
|
+
if (wantR("readline")) {
|
|
123
154
|
const id = `r${++seq}`;
|
|
124
155
|
const from = frames.length;
|
|
125
156
|
page.send({ type: "exec", id, source: 'ans <- readline("Your name: "); cat("hello", ans, "\\n")' });
|
|
@@ -135,7 +166,7 @@ if (want("readline")) {
|
|
|
135
166
|
|
|
136
167
|
// ── while busy ───────────────────────────────────────────────────────────────
|
|
137
168
|
const ask = (type, payload, ms) => session.ask(type, payload, ms);
|
|
138
|
-
if (
|
|
169
|
+
if (wantR("busy")) {
|
|
139
170
|
fs.writeFileSync(path.join(scratch, "note.txt"), "hello");
|
|
140
171
|
const id = `b${++seq}`;
|
|
141
172
|
const from = frames.length;
|
|
@@ -160,7 +191,7 @@ if (want("busy")) {
|
|
|
160
191
|
}
|
|
161
192
|
|
|
162
193
|
// ── force stop ───────────────────────────────────────────────────────────────
|
|
163
|
-
if (
|
|
194
|
+
if (wantR("force")) {
|
|
164
195
|
await run("keep <- 42");
|
|
165
196
|
const id = `f${++seq}`;
|
|
166
197
|
const from = frames.length;
|
|
@@ -179,7 +210,7 @@ if (want("force")) {
|
|
|
179
210
|
// R dies under a running chunk (kill -9, as a segfault or the OOM killer would):
|
|
180
211
|
// the chunk must END with a sentence, the page must be told, and a fresh R must
|
|
181
212
|
// answer — never a spinner that waits for a process that is gone.
|
|
182
|
-
if (
|
|
213
|
+
if (wantR("crash")) {
|
|
183
214
|
const probe = await run('cat("pid=", Sys.getpid(), "\\n")');
|
|
184
215
|
const pid = Number((probe.out().match(/pid= (\d+)/) || [])[1]);
|
|
185
216
|
const id = `c${++seq}`;
|
|
@@ -211,7 +242,7 @@ function rssKb(pid) {
|
|
|
211
242
|
const v = Number(String(r.stdout).trim());
|
|
212
243
|
return Number.isFinite(v) && v > 0 ? v : null;
|
|
213
244
|
}
|
|
214
|
-
if (
|
|
245
|
+
if (wantR("memory")) {
|
|
215
246
|
const rounds = Math.max(4, Number(argv.memoryRounds || 8));
|
|
216
247
|
const samples = [];
|
|
217
248
|
await run("df <- data.frame(id = seq_len(5000), x = rnorm(5000), g = sample(letters, 5000, TRUE))");
|
|
@@ -251,7 +282,7 @@ for (const [engine, setup, probe, sleeper] of [
|
|
|
251
282
|
["js", "globalThis.keepJs = 42; console.log('js keep', keepJs)", "console.log('js keep', keepJs)", "for (let i = 0; i < 1e13; i++) {}\nconsole.log('after-stop')"],
|
|
252
283
|
]) {
|
|
253
284
|
if (!want(engine)) continue;
|
|
254
|
-
const row = Array.isArray(
|
|
285
|
+
const row = Array.isArray(announced.engine_status) ? announced.engine_status.find((e) => e.engine === engine) : null;
|
|
255
286
|
if (!engines.includes(engine)) { record(engine, `${engine} chunks`, "skip", row ? row.detail || "engine not available" : "this host has no such engine"); continue; }
|
|
256
287
|
const first = await run(setup, { engine, ms: 120000 });
|
|
257
288
|
record(engine, `a ${engine} chunk runs (first run starts the engine)`, first.done?.status === "ok" && /keep 42/.test(first.out()) ? "pass" : "fail", first.out().trim() || first.done?.message || "", first.ms);
|
|
@@ -1,3 +1,29 @@
|
|
|
1
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
2
|
+
var __esm = (fn, res, err) => function __init() {
|
|
3
|
+
if (err) throw err[0];
|
|
4
|
+
try {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
} catch (e) {
|
|
7
|
+
throw err = [e], e;
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// lib/engine-labels.js
|
|
12
|
+
var KNOWN_ENGINES, BY_KEY, AUTHORING_ENGINES;
|
|
13
|
+
var init_engine_labels = __esm({
|
|
14
|
+
"lib/engine-labels.js"() {
|
|
15
|
+
KNOWN_ENGINES = Object.freeze([
|
|
16
|
+
Object.freeze({ key: "r", label: "R" }),
|
|
17
|
+
Object.freeze({ key: "python", label: "Python" }),
|
|
18
|
+
Object.freeze({ key: "js", label: "JavaScript" }),
|
|
19
|
+
Object.freeze({ key: "bash", label: "Bash" }),
|
|
20
|
+
Object.freeze({ key: "sql", label: "SQL" })
|
|
21
|
+
]);
|
|
22
|
+
BY_KEY = new Map(KNOWN_ENGINES.map((e) => [e.key, e.label]));
|
|
23
|
+
AUTHORING_ENGINES = Object.freeze(KNOWN_ENGINES.map((e) => e.key));
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
1
27
|
// tools/beatrina/knit-file.mjs
|
|
2
28
|
import fs from "node:fs";
|
|
3
29
|
import path from "node:path";
|
|
@@ -4467,7 +4493,10 @@ var BEATRINA_CSS = `
|
|
|
4467
4493
|
.cn-root .beatrina-md-tool:focus-visible{outline:0;box-shadow:var(--cn-focus-ring);}
|
|
4468
4494
|
.cn-root .beatrina-md-tool.is-cite{color:var(--cn-accent);}
|
|
4469
4495
|
.cn-root .beatrina-md-tool-label{font:600 var(--cn-fs-sm) var(--cn-font);}
|
|
4470
|
-
|
|
4496
|
+
/* width as well as flex-basis: WebKit leaves a basis-only item out of the bar's max-content
|
|
4497
|
+
width, so the bar came out 4 px short at every fold level and folded everything behind \u22EE
|
|
4498
|
+
(Beatrina's window, 2026-09-23). */
|
|
4499
|
+
.cn-root .beatrina-md-bar-sp{flex:0 0 4px;width:4px;}
|
|
4471
4500
|
.cn-root .beatrina-md-count{
|
|
4472
4501
|
font-size:var(--cn-fs-sm);opacity:.55;font-variant-numeric:tabular-nums;margin-right:2px;
|
|
4473
4502
|
}
|
|
@@ -10637,6 +10666,14 @@ var BEATRINA_CSS = `
|
|
|
10637
10666
|
@media(max-width:760px){
|
|
10638
10667
|
.cn-root.has-ai-left{padding-left:0;}
|
|
10639
10668
|
.cn-root.has-ai-right{padding-right:0;}
|
|
10669
|
+
/* \u2026and so a docked pane is a SHEET over the whole window, not a 400 px
|
|
10670
|
+
slab over the right half of the words. Reserving its width at this size
|
|
10671
|
+
would leave a column of ~260 px; overlaying half the text left every line
|
|
10672
|
+
cut off under it (journey windows, 720 px, 2026-09-24). One or the other
|
|
10673
|
+
is on screen; \xD7 gives the document back. !important beats the inline
|
|
10674
|
+
left/right/width ai-shell.js writes for the wide layout. */
|
|
10675
|
+
.cn-root .beatrina-ai-panel.is-side{left:0 !important;right:0 !important;width:auto !important;}
|
|
10676
|
+
.cn-root .beatrina-ai-panel.is-side .beatrina-ai-sedge{display:none;}
|
|
10640
10677
|
}
|
|
10641
10678
|
|
|
10642
10679
|
.cn-root .beatrina-aiw-convbtn{flex:0 0 auto;width:24px;height:24px;
|
|
@@ -11777,7 +11814,7 @@ body:has(.cn-root[data-beatrina-mode="writing"] > .beatrina-writing-bar){--beatr
|
|
|
11777
11814
|
box-shadow:0 -1px 0 var(--cn-border,#e3e3e3);
|
|
11778
11815
|
}
|
|
11779
11816
|
.beatrina-dock[hidden]{display:none;}
|
|
11780
|
-
.beatrina-dock[data-mode="bottom"]{left:
|
|
11817
|
+
.beatrina-dock[data-mode="bottom"]{left:var(--beatrina-ai-edge-left,0px);right:var(--beatrina-ai-edge-right,0px);bottom:0;flex-direction:column;}
|
|
11781
11818
|
.beatrina-dock[data-mode="right"]{
|
|
11782
11819
|
top:0;right:0;bottom:0;flex-direction:row;
|
|
11783
11820
|
box-shadow:-1px 0 0 var(--cn-border,#e3e3e3);
|
|
@@ -14905,7 +14942,8 @@ ${out}</div>
|
|
|
14905
14942
|
if (!ctx.rawUrls && !safeImageSrc(url)) {
|
|
14906
14943
|
return `<span class="beatrina-md-badsrc">[image: ${alt || "unsupported source"}]</span>`;
|
|
14907
14944
|
}
|
|
14908
|
-
|
|
14945
|
+
const local = ctx.localImages && /^(?![a-z][a-z0-9+.-]*:|\/\/|#)/i.test(url.trim());
|
|
14946
|
+
let out = local ? `<img data-local-src="${attrValue(url)}" alt="${alt}"` : `<img src="${attrValue(encodeHref(url))}" alt="${alt}"`;
|
|
14909
14947
|
if (node2.title) out += ` title="${attrValue(node2.title)}"`;
|
|
14910
14948
|
if (!ctx.spec) out += ' class="beatrina-md-img" loading="lazy"';
|
|
14911
14949
|
return ctx.spec ? `${out} />` : `${out}>`;
|
|
@@ -14994,6 +15032,7 @@ function toHtml(tree, opts = {}) {
|
|
|
14994
15032
|
// survived a JSON round-trip or a parsed query, does not open it.
|
|
14995
15033
|
rawHtml: opts.allowDangerousHtml === true,
|
|
14996
15034
|
rawUrls: opts.allowDangerousProtocol === true,
|
|
15035
|
+
localImages: opts.localImages === true,
|
|
14997
15036
|
gfm: opts.gfm !== false,
|
|
14998
15037
|
softBreak: typeof opts.softBreak === "string" ? opts.softBreak : "\n",
|
|
14999
15038
|
state: opts.state || {},
|
|
@@ -18697,17 +18736,6 @@ function citeStyleOf(notebook) {
|
|
|
18697
18736
|
};
|
|
18698
18737
|
}
|
|
18699
18738
|
|
|
18700
|
-
// lib/engine-labels.js
|
|
18701
|
-
var KNOWN_ENGINES = Object.freeze([
|
|
18702
|
-
Object.freeze({ key: "r", label: "R" }),
|
|
18703
|
-
Object.freeze({ key: "python", label: "Python" }),
|
|
18704
|
-
Object.freeze({ key: "js", label: "JavaScript" }),
|
|
18705
|
-
Object.freeze({ key: "bash", label: "Bash" }),
|
|
18706
|
-
Object.freeze({ key: "sql", label: "SQL" })
|
|
18707
|
-
]);
|
|
18708
|
-
var BY_KEY = new Map(KNOWN_ENGINES.map((e) => [e.key, e.label]));
|
|
18709
|
-
var AUTHORING_ENGINES = Object.freeze(KNOWN_ENGINES.map((e) => e.key));
|
|
18710
|
-
|
|
18711
18739
|
// lib/doc-mode.js
|
|
18712
18740
|
var MODES = Object.freeze(["development", "writing", "latex"]);
|
|
18713
18741
|
var MODE_KEY = "beatrina-mode";
|
|
@@ -19671,6 +19699,10 @@ ${references}
|
|
|
19671
19699
|
`;
|
|
19672
19700
|
}
|
|
19673
19701
|
|
|
19702
|
+
// lib/chunk-meta.js
|
|
19703
|
+
init_engine_labels();
|
|
19704
|
+
init_engine_labels();
|
|
19705
|
+
|
|
19674
19706
|
// src/r-kernel.js
|
|
19675
19707
|
function normalizeTable(frame) {
|
|
19676
19708
|
const rows = Array.isArray(frame.rows) ? frame.rows : frame.rows ? [frame.rows] : [];
|
package/kernel/check/session.mjs
CHANGED
|
@@ -143,8 +143,14 @@ export function freePort() {
|
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
/**
|
|
146
|
-
* Start a supervisor on `port`, open a page socket, and wait for
|
|
147
|
-
*
|
|
146
|
+
* Start a supervisor on `port`, open a page socket, and wait for it to be ready.
|
|
147
|
+
*
|
|
148
|
+
* A host that speaks the handshake sends `host-ready` first (plans/
|
|
149
|
+
* getting_rid_of_R.md K2). When its R row says R is NOT coming — absent, or
|
|
150
|
+
* failed to start — there is no R `ready` to wait for, and the session is the
|
|
151
|
+
* HOST: `ready` stays null, `hostReady` is the frame, `noR` says why. An older
|
|
152
|
+
* host sends no host-ready and is waited on for R's ready, as before.
|
|
153
|
+
* @returns {Promise<object>} the session: frames, until, text, run, ask, ready, hostReady, noR, startupMs, log, shutdown
|
|
148
154
|
*/
|
|
149
155
|
export async function startSession({ command, port, cwd, env = {}, readyMs = 90000, logFile = "" }) {
|
|
150
156
|
const t0 = Date.now();
|
|
@@ -214,7 +220,7 @@ export async function startSession({ command, port, cwd, env = {}, readyMs = 900
|
|
|
214
220
|
else { try { process.kill(child.pid, "SIGKILL"); } catch { /* gone */ } }
|
|
215
221
|
}
|
|
216
222
|
};
|
|
217
|
-
const session = { child, port, announced, page: null, frames: [], ready: null, startupMs: null, log: () => hostLog, shutdown };
|
|
223
|
+
const session = { child, port, announced, page: null, frames: [], ready: null, hostReady: null, noR: "", startupMs: null, log: () => hostLog, shutdown };
|
|
218
224
|
if (!announced) return session;
|
|
219
225
|
|
|
220
226
|
const page = await openSocket(port, `http://127.0.0.1:${port}`);
|
|
@@ -250,7 +256,16 @@ export async function startSession({ command, port, cwd, env = {}, readyMs = 900
|
|
|
250
256
|
const reply = await session.until((f) => f.id === id && f.type !== "stdout" && f.type !== "stderr", ms, from);
|
|
251
257
|
return { reply, ms: Date.now() - t };
|
|
252
258
|
};
|
|
253
|
-
|
|
259
|
+
// host-ready is the first frame on open, or never (an older host): a short wait decides.
|
|
260
|
+
session.hostReady = await session.until((f) => f.type === "host-ready" || f.type === "ready", 15000);
|
|
261
|
+
if (session.hostReady && session.hostReady.type === "ready") { session.ready = session.hostReady; session.hostReady = null; }
|
|
262
|
+
const rRow = session.hostReady && Array.isArray(session.hostReady.engine_status)
|
|
263
|
+
? session.hostReady.engine_status.find((e) => e.engine === "r") : null;
|
|
264
|
+
if (session.hostReady && (!rRow || !rRow.available || rRow.error)) {
|
|
265
|
+
session.noR = (rRow && (rRow.error || rRow.detail)) || "this host has no R engine";
|
|
266
|
+
} else if (!session.ready) {
|
|
267
|
+
session.ready = await session.until((f) => f.type === "ready", readyMs);
|
|
268
|
+
}
|
|
254
269
|
session.startupMs = Date.now() - t0;
|
|
255
270
|
return session;
|
|
256
271
|
}
|
package/kernel/kernel-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
1.0.0
|
package/lib/engine-labels.js
CHANGED
|
@@ -118,6 +118,8 @@ export function versionMenuLabel(engine) {
|
|
|
118
118
|
*/
|
|
119
119
|
export function kernelLanguage(info) {
|
|
120
120
|
const declared = info && typeof info.language === "string" ? info.language.trim() : "";
|
|
121
|
+
// A host running no default engine (host-ready) runs no language: "".
|
|
122
|
+
if (!declared && info && info.host_only) return "";
|
|
121
123
|
return declared ? engineKey(declared) : DEFAULT_ENGINE;
|
|
122
124
|
}
|
|
123
125
|
|
|
@@ -154,6 +156,8 @@ export function kernelVersion(info) {
|
|
|
154
156
|
* has always shown for a listener that named no version.
|
|
155
157
|
*/
|
|
156
158
|
export function kernelDisplay(info) {
|
|
159
|
+
// The HOST is attached and no engine is its own (plans/getting_rid_of_R.md K2).
|
|
160
|
+
if (!kernelLanguage(info) && info) return "Beatrina";
|
|
157
161
|
const label = engineLabel(kernelLanguage(info));
|
|
158
162
|
const version = kernelVersion(info);
|
|
159
163
|
return version ? `${label} ${version}` : `${label} session`;
|
|
@@ -216,6 +220,8 @@ export function reportedEngines(info) {
|
|
|
216
220
|
if (row && !rows.some((seen) => seen.name === row.name)) rows.push(row);
|
|
217
221
|
});
|
|
218
222
|
if (!rows.length) return [];
|
|
223
|
+
// A host with no language of its own adds none.
|
|
224
|
+
if (!language) return rows;
|
|
219
225
|
// The kernel's own language is always one of its engines — a session that
|
|
220
226
|
// would not run its own language is not a thing, and a list that omitted it
|
|
221
227
|
// would strand every chunk in the notebook.
|
|
@@ -239,7 +245,7 @@ export function reportedEngines(info) {
|
|
|
239
245
|
*/
|
|
240
246
|
export function declaredEngines(info) {
|
|
241
247
|
const language = kernelLanguage(info);
|
|
242
|
-
const out = [language];
|
|
248
|
+
const out = language ? [language] : [];
|
|
243
249
|
reportedEngines(info).forEach((row) => { if (!out.includes(row.name)) out.push(row.name); });
|
|
244
250
|
return out;
|
|
245
251
|
}
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
<key>CFBundleExecutable</key><string>Beatrina Menu</string>
|
|
7
7
|
<key>CFBundleIdentifier</key><string>me.saqr.beatrina.menu</string>
|
|
8
8
|
<key>CFBundlePackageType</key><string>APPL</string>
|
|
9
|
-
<key>CFBundleShortVersionString</key><string>0.
|
|
10
|
-
<key>CFBundleVersion</key><string>0.
|
|
9
|
+
<key>CFBundleShortVersionString</key><string>1.0.0</string>
|
|
10
|
+
<key>CFBundleVersion</key><string>1.0.0</string>
|
|
11
11
|
<key>LSMinimumSystemVersion</key><string>13.0</string>
|
|
12
12
|
<!-- Menu bar only: no Dock tile, no window, nothing to close by accident. -->
|
|
13
13
|
<key>LSUIElement</key><true/>
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "beatrina",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Beatrina is a modern, powerful, feature-rich integrated development environment (IDE) for R, Python, and JavaScript.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -23,9 +23,10 @@
|
|
|
23
23
|
"failsafe",
|
|
24
24
|
"lib",
|
|
25
25
|
"menu",
|
|
26
|
+
"app",
|
|
26
27
|
"build-info.json",
|
|
27
|
-
"
|
|
28
|
-
"
|
|
28
|
+
"beatrina_V1.0.0.html",
|
|
29
|
+
"beatrina_V1.0.0.html.inputs.json",
|
|
29
30
|
"README.md",
|
|
30
31
|
"LICENSE",
|
|
31
32
|
"NOTICES"
|