@jossuealcala/madre 0.7.2 → 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/CHANGELOG.md +773 -1
- package/README.md +92 -28
- package/bin/madre.mjs +30 -10
- package/docs/REFERENCE.md +157 -81
- package/docs/SDK.md +23 -19
- package/package.json +1 -1
- package/public/app.js +11207 -4196
- package/public/assets/brand/Geist-latin-ext.woff2 +0 -0
- package/public/assets/brand/Geist-latin.woff2 +0 -0
- package/public/assets/brand/GeistMono-latin-ext.woff2 +0 -0
- package/public/assets/brand/GeistMono-latin.woff2 +0 -0
- package/public/assets/brand/JetBrainsMono-latin-ext.woff2 +0 -0
- package/public/assets/brand/JetBrainsMono-latin.woff2 +0 -0
- package/public/assets/brand/OFL-Geist.txt +93 -0
- package/public/assets/brand/OFL-GeistMono.txt +93 -0
- package/public/assets/brand/OFL-JetBrainsMono.txt +93 -0
- package/public/assets/brand/OFL-Sora.txt +93 -0
- package/public/assets/brand/Sora-latin-ext.woff2 +0 -0
- package/public/assets/brand/Sora-latin.woff2 +0 -0
- package/public/assets/brand/madre-app-180.png +0 -0
- package/public/assets/brand/madre-app-192.png +0 -0
- package/public/assets/brand/madre-app-512.png +0 -0
- package/public/assets/brand/madre-app.svg +1 -0
- package/public/assets/brand/madre-favicon.svg +1 -0
- package/public/assets/news/1.0/alerts.webp +0 -0
- package/public/assets/news/1.0/bar.webp +0 -0
- package/public/assets/news/1.0/finder.webp +0 -0
- package/public/assets/news/1.0/github.webp +0 -0
- package/public/assets/news/1.0/intents.webp +0 -0
- package/public/assets/news/1.0/looks.webp +0 -0
- package/public/assets/news/1.0/modules.webp +0 -0
- package/public/assets/news/1.0/mother.webp +0 -0
- package/public/assets/news/1.0/nostromo.webp +0 -0
- package/public/assets/news/1.0/permissions.webp +0 -0
- package/public/assets/news/1.0/room.webp +0 -0
- package/public/assets/news/1.0/settings.webp +0 -0
- package/public/assets/wallpapers/biznaga.webp +0 -0
- package/public/assets/wallpapers/canela.webp +0 -0
- package/public/assets/wallpapers/jarrito.webp +0 -0
- package/public/assets/wallpapers/magna.webp +0 -0
- package/public/assets/wallpapers/oceano.webp +0 -0
- package/public/assets/wallpapers/paraiso.webp +0 -0
- package/public/assets/wallpapers/quila.webp +0 -0
- package/public/assets/wallpapers/tradicion.webp +0 -0
- package/public/assets/wallpapers/tropical.webp +0 -0
- package/public/assets/wallpapers/urbania.webp +0 -0
- package/public/assets/wallpapers/ventura.webp +0 -0
- package/public/assets/wallpapers/xico.webp +0 -0
- package/public/brands.js +28 -2
- package/public/editor.js +744 -0
- package/public/es.js +2784 -1026
- package/public/i18n.js +1 -1
- package/public/index.html +201 -175
- package/public/inquiry.js +11 -11
- package/public/loader.js +79 -0
- package/public/macroverso.js +1497 -0
- package/public/manifest.webmanifest +14 -0
- package/public/nostromo-engine.js +2222 -0
- package/public/resay.js +24 -0
- package/public/review-ask.js +85 -0
- package/public/salas-mac.css +787 -0
- package/public/salas-mac.html +154 -0
- package/public/salas-mac.js +3264 -0
- package/public/salas.css +85 -0
- package/public/salas.html +47 -0
- package/public/salas.js +200 -0
- package/public/styles.css +6207 -425
- package/public/touch.js +83 -0
- package/public/tour-chapters.js +342 -0
- package/public/tour.js +510 -0
- package/public/troubleshooting.js +159 -140
- package/src/adapters/codex.mjs +46 -6
- package/src/adapters/gemini.mjs +30 -10
- package/src/adapters/madre.mjs +110 -18
- package/src/adapters/process.mjs +42 -2
- package/src/auth-probe.mjs +2 -2
- package/src/bridge/devices.mjs +202 -0
- package/src/bridge/handoff.mjs +45 -0
- package/src/bridge/host.mjs +82 -0
- package/src/bridge/network.mjs +133 -0
- package/src/bridge/policy.mjs +89 -0
- package/src/bridge/qr.mjs +287 -0
- package/src/bridge/server.mjs +269 -0
- package/src/capabilities.mjs +5 -9
- package/src/chats.mjs +59 -22
- package/src/checkpoint.mjs +137 -35
- package/src/choices.mjs +18 -3
- package/src/cli-args.mjs +12 -1
- package/src/commands.mjs +15 -15
- package/src/config.mjs +73 -9
- package/src/conversation-context.mjs +5 -2
- package/src/dataset.mjs +3 -1
- package/src/directives.mjs +0 -5
- package/src/enlaces.mjs +451 -0
- package/src/event-store.mjs +55 -10
- package/src/exam.mjs +5 -5
- package/src/extensions.mjs +0 -2
- package/src/file-ops.mjs +23 -0
- package/src/files.mjs +52 -2
- package/src/github.mjs +407 -0
- package/src/json-file.mjs +63 -0
- package/src/lease.mjs +2 -2
- package/src/lobby-work.mjs +103 -0
- package/src/looks.mjs +55 -0
- package/src/maturity.mjs +3 -3
- package/src/mcp/github-server.mjs +164 -0
- package/src/mcp/image-server.mjs +1 -1
- package/src/mcp/memory-server.mjs +11 -3
- package/src/memory.mjs +38 -12
- package/src/models.mjs +89 -5
- package/src/modules/ahp.mjs +4 -4
- package/src/modules/ash.mjs +1 -1
- package/src/modules/bishop-plan.mjs +207 -0
- package/src/modules/bishop.mjs +645 -0
- package/src/modules/choices.mjs +2 -2
- package/src/modules/correo.mjs +1 -1
- package/src/modules/enlaces.mjs +255 -0
- package/src/modules/git-pulse.mjs +2 -2
- package/src/modules/github.mjs +322 -0
- package/src/modules/image-studio.mjs +3 -3
- package/src/modules/index.mjs +18 -4
- package/src/modules/intents-calibration.mjs +338 -0
- package/src/modules/intents-desk.mjs +481 -0
- package/src/modules/intents-observe.mjs +266 -0
- package/src/modules/intents-policy.mjs +414 -0
- package/src/modules/intents-try.mjs +91 -0
- package/src/modules/intents.mjs +145 -0
- package/src/modules/ollama.mjs +4 -4
- package/src/modules/playwright.mjs +121 -11
- package/src/modules/ripley.mjs +1 -1
- package/src/modules/runs.mjs +2 -2
- package/src/modules/sdk.mjs +10 -4
- package/src/ollama.mjs +6 -1
- package/src/outbound.mjs +19 -17
- package/src/override.mjs +100 -0
- package/src/ports.mjs +23 -1
- package/src/prices.mjs +72 -0
- package/src/privacy.mjs +30 -11
- package/src/quota-sources.mjs +0 -8
- package/src/request-guard.mjs +1 -1
- package/src/review-ask.mjs +111 -0
- package/src/room/archivist.mjs +55 -37
- package/src/room/budget.mjs +17 -5
- package/src/room/control.mjs +70 -7
- package/src/room/economy.mjs +35 -7
- package/src/room/escalation.mjs +2 -0
- package/src/room/guard.mjs +26 -2
- package/src/room/prompt.mjs +22 -15
- package/src/room/runs.mjs +84 -21
- package/src/room/vectors.mjs +19 -4
- package/src/room.mjs +351 -65
- package/src/router.mjs +3 -1
- package/src/runs.mjs +23 -5
- package/src/salas.mjs +313 -0
- package/src/sentinel-errors.mjs +1 -1
- package/src/server-sentinel.mjs +124 -0
- package/src/server.mjs +1526 -163
- package/src/setup.mjs +1 -0
- package/src/tour.mjs +159 -0
- package/src/updates.mjs +48 -8
- package/src/usage-sentinel.mjs +1 -1
- package/src/verdict.mjs +7 -7
- package/src/whats-new.mjs +160 -0
package/public/salas.css
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/* ROOMS, on the phone (salas.html). The tokens are the room's (styles.css, loaded first); this only
|
|
2
|
+
lays out one column for a thumb: the rooms as AJUSTES-style blocks, the one that asks for you in
|
|
3
|
+
the accent, and the edges of the screen kept clear of the notch and the home bar. */
|
|
4
|
+
body.salas {
|
|
5
|
+
--s-acc: var(--acc, var(--phosphor));
|
|
6
|
+
--s-acc-ink: var(--acc-ink, var(--phosphor-ink, #0a1405));
|
|
7
|
+
min-height: 100dvh; margin: 0; background: var(--bg); color: var(--text);
|
|
8
|
+
font-family: var(--read); font-size: 15px; line-height: 1.45;
|
|
9
|
+
padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
|
|
10
|
+
}
|
|
11
|
+
.salas button { font: inherit; color: inherit; }
|
|
12
|
+
.salas-top { position: sticky; top: 0; z-index: 2; display: flex; align-items: center; gap: 10px; padding: 14px 18px 12px; background: color-mix(in srgb, var(--bg) 92%, transparent); }
|
|
13
|
+
/* The brand as the bar draws it on the Mac, drawn once and then still. */
|
|
14
|
+
.salas-brand { display: inline-flex; align-items: center; flex: none; }
|
|
15
|
+
.salas-brand .brand-logo { height: 15px; }
|
|
16
|
+
.salas-mac { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12.5px; color: var(--text-2); }
|
|
17
|
+
.salas-stop { display: inline-flex; align-items: center; gap: 6px; height: 34px; padding: 0 14px 0 11px; border: 0; border-radius: 999px; background: color-mix(in srgb, var(--terror, #ff4d4d) 14%, transparent); color: var(--terror, #ff4d4d); font-size: 13px !important; font-weight: 600; cursor: pointer; }
|
|
18
|
+
.salas-stop svg { width: 15px; height: 15px; }
|
|
19
|
+
.salas-stop.armed { background: var(--terror, #ff4d4d); color: #fff; }
|
|
20
|
+
.salas-stop[hidden] { display: none; }
|
|
21
|
+
|
|
22
|
+
.salas-main { max-width: 560px; margin: 0 auto; padding: 4px 16px 32px; }
|
|
23
|
+
.salas-title { margin: 14px 2px 10px; font-size: 26px; font-weight: 700; letter-spacing: -.01em; }
|
|
24
|
+
.salas-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
|
|
25
|
+
.salas-room {
|
|
26
|
+
display: grid; gap: 3px; width: 100%; padding: 14px 16px; border: 0; border-radius: 18px; text-align: left; cursor: pointer;
|
|
27
|
+
background: var(--panel); box-shadow: var(--raise, 0 1px 2px rgba(0, 0, 0, .12), 0 8px 20px -14px rgba(0, 0, 0, .4));
|
|
28
|
+
transition: transform var(--motion-fast, 140ms) var(--ease-out, ease);
|
|
29
|
+
}
|
|
30
|
+
.salas-room:active:not(:disabled) { transform: scale(.985); }
|
|
31
|
+
.salas-room:disabled { cursor: default; opacity: .72; }
|
|
32
|
+
.salas-room-head { display: flex; align-items: baseline; gap: 10px; }
|
|
33
|
+
.salas-room-head b { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 16.5px; font-weight: 650; }
|
|
34
|
+
.salas-room-head time { flex: none; font-size: 12px; color: var(--text-3); font-variant-numeric: tabular-nums; }
|
|
35
|
+
.salas-room-chat { font-size: 13.5px; font-weight: 500; color: var(--text-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
36
|
+
.salas-room-preview { font-size: 13.5px; color: var(--text-2); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
|
|
37
|
+
.salas-room-note { font-size: 13px; color: var(--text-2); }
|
|
38
|
+
/* The archive, in NOSTROMO's colours: one line per room, as long as each kind is large. */
|
|
39
|
+
.salas-room-strip { display: flex; gap: 2px; height: 5px; margin-top: 8px; border-radius: 3px; overflow: hidden; }
|
|
40
|
+
.salas-room-strip i { min-width: 3px; }
|
|
41
|
+
.salas-room-strip .k-decision { background: var(--mem-decision); }
|
|
42
|
+
.salas-room-strip .k-fact { background: var(--mem-fact); }
|
|
43
|
+
.salas-room-strip .k-preference { background: var(--mem-preference); }
|
|
44
|
+
.salas-room-strip .k-question { background: var(--mem-question); }
|
|
45
|
+
.salas-room-strip .k-aberration { background: var(--mem-aberration); }
|
|
46
|
+
:root[data-scheme="light"] .salas-room-strip .k-fact { background: #b9c6d6; }
|
|
47
|
+
.salas-room-count { margin-top: 3px; font-size: 12px; color: var(--text-3); }
|
|
48
|
+
/* A room that waits for you wears the accent: a ring and the line that says what it waits for. */
|
|
49
|
+
.salas-room.waiting { box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--s-acc) 70%, transparent), var(--raise, 0 1px 2px rgba(0, 0, 0, .12)); }
|
|
50
|
+
.salas-room-wait { display: grid; gap: 2px; margin: 4px 0 2px; padding: 9px 12px; border-radius: 12px; background: color-mix(in srgb, var(--s-acc) 16%, transparent); }
|
|
51
|
+
.salas-room-wait b { font-size: 12.5px; font-weight: 700; color: color-mix(in srgb, var(--s-acc) 62%, var(--text)); }
|
|
52
|
+
.salas-room-wait span { font-size: 13.5px; color: var(--text); overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
|
|
53
|
+
.salas-room-busy { display: inline-flex; align-items: center; gap: 7px; margin-top: 6px; justify-self: start; padding: 3px 10px 3px 8px; border-radius: 999px; background: color-mix(in srgb, var(--s-acc) 16%, transparent); font-size: 12.5px; font-weight: 600; color: color-mix(in srgb, var(--s-acc) 60%, var(--text)); }
|
|
54
|
+
.salas-room-busy i { width: 7px; height: 7px; border-radius: 50%; background: var(--s-acc); animation: salas-beat 1.4s ease-in-out infinite; }
|
|
55
|
+
@keyframes salas-beat { 50% { opacity: .35; transform: scale(.8); } }
|
|
56
|
+
|
|
57
|
+
.salas-continue { display: flex; align-items: center; gap: 12px; width: 100%; margin: 6px 0 4px; padding: 14px 16px; border: 0; border-radius: 18px; text-align: left; cursor: pointer; background: color-mix(in srgb, var(--s-acc) 15%, var(--panel)); box-shadow: var(--raise, 0 1px 2px rgba(0, 0, 0, .12)); }
|
|
58
|
+
.salas-continue[hidden] { display: none; }
|
|
59
|
+
.salas-continue-words { flex: 1; min-width: 0; display: grid; gap: 2px; }
|
|
60
|
+
.salas-continue-words b { font-size: 15px; font-weight: 650; }
|
|
61
|
+
.salas-continue-words span { font-size: 13px; color: var(--text-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
62
|
+
.salas-continue-draft { font-style: italic; }
|
|
63
|
+
.salas-continue-go { flex: none; font-size: 26px; line-height: 1; color: color-mix(in srgb, var(--s-acc) 70%, var(--text)); }
|
|
64
|
+
|
|
65
|
+
.salas-empty, .salas-foot { margin: 18px 4px 0; font-size: 13px; color: var(--text-2); text-align: center; }
|
|
66
|
+
.salas-foot { color: var(--text-3); font-size: 12px; }
|
|
67
|
+
|
|
68
|
+
.salas-welcome { position: fixed; inset: 0; z-index: 10; display: grid; align-items: end; background: color-mix(in srgb, #000 40%, transparent); padding: 0 10px calc(10px + env(safe-area-inset-bottom)); }
|
|
69
|
+
.salas-welcome[hidden] { display: none; }
|
|
70
|
+
.salas-sheet { justify-self: center; width: min(460px, 100%); padding: 22px 20px 18px; border-radius: 24px; background: var(--panel); box-shadow: 0 20px 50px -20px rgba(0, 0, 0, .5); text-align: center; animation: salas-up 240ms var(--ease-out, ease-out); }
|
|
71
|
+
@keyframes salas-up { from { transform: translateY(24px); opacity: 0; } }
|
|
72
|
+
.salas-check { display: inline-grid; place-items: center; width: 52px; height: 52px; border-radius: 50%; background: var(--s-acc); color: var(--s-acc-ink); }
|
|
73
|
+
.salas-check svg { width: 28px; height: 28px; }
|
|
74
|
+
.salas-sheet h2 { margin: 12px 0 6px; font-size: 20px; font-weight: 700; }
|
|
75
|
+
.salas-sheet p { margin: 0; color: var(--text-2); font-size: 14.5px; }
|
|
76
|
+
.salas-steps { margin: 12px 0 16px; padding: 12px 14px 12px 34px; border-radius: 14px; background: color-mix(in srgb, var(--text) 5%, transparent); text-align: left; font-size: 14.5px; display: grid; gap: 6px; }
|
|
77
|
+
.salas-primary { width: 100%; height: 46px; border: 0; border-radius: 14px; background: var(--s-acc); color: var(--s-acc-ink) !important; font-weight: 650; font-size: 15.5px !important; cursor: pointer; }
|
|
78
|
+
|
|
79
|
+
/* A page of one sentence: a phone that is not paired, or a code that no longer works. */
|
|
80
|
+
body.salas.note main { max-width: 420px; margin: 0 auto; padding: 22vh 24px 0; text-align: center; }
|
|
81
|
+
body.salas.note h1 { font-size: 22px; margin: 0 0 10px; }
|
|
82
|
+
body.salas.note p { color: var(--text-2); }
|
|
83
|
+
@media (prefers-reduced-motion: reduce) { .salas-room-busy i, .salas-sheet { animation: none; } .salas-room { transition: none; } }
|
|
84
|
+
/* The ground under Safari's bars is the page's own. */
|
|
85
|
+
html:has(body.salas) { background: var(--bg); }
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="es">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
|
6
|
+
<meta name="theme-color" content="#050605">
|
|
7
|
+
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
8
|
+
<meta name="mobile-web-app-capable" content="yes">
|
|
9
|
+
<meta name="apple-mobile-web-app-title" content="MADRE">
|
|
10
|
+
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
|
11
|
+
<title>MADRE</title>
|
|
12
|
+
<link rel="manifest" href="/bridge/manifest.webmanifest">
|
|
13
|
+
<link rel="icon" type="image/svg+xml" href="/assets/brand/madre-favicon.svg">
|
|
14
|
+
<link rel="apple-touch-icon" href="/assets/brand/madre-app-180.png">
|
|
15
|
+
<link rel="stylesheet" href="/bridge/styles.css">
|
|
16
|
+
<link rel="stylesheet" href="/bridge/salas.css">
|
|
17
|
+
</head>
|
|
18
|
+
<body class="salas">
|
|
19
|
+
<header class="salas-top">
|
|
20
|
+
<span class="salas-brand" aria-label="MADRE">
|
|
21
|
+
<svg class="brand-logo" viewBox="-4.8 -4.8 377.1 49.6" aria-hidden="true"><g fill="none" stroke-linecap="round" stroke-linejoin="round"><g class="pulse" transform="translate(0 0) scale(2.5) translate(0 -2)"><path class="beat" d="M1 10H16L20 10L23.5 2L28 18L32 10L35 10L37 6L39 10H52" stroke-width="1.92" pathLength="100"/><circle class="beat-dot" cx="60" cy="10" r="3" stroke="none"/></g><g class="word" transform="translate(193.5 7)" stroke-width="4.8"><path d="M0 26V0L14 14.3L28 0V26"/><path transform="translate(36.5 0)" d="M0 26V7.8A7.8 7.8 0 0 1 7.8 0H17.2A7.8 7.8 0 0 1 25 7.8V26M0 15.6H25"/><path transform="translate(71.5 0)" d="M0 0H16A12 12 0 0 1 28 12V14A12 12 0 0 1 16 26H0Z"/><path transform="translate(110.5 0)" d="M0 26V0H20.85A7.15 7.15 0 0 1 28 7.15V7.15A7.15 7.15 0 0 1 20.85 14.3H0M15.12 14.3L28 26"/><path transform="translate(146 0)" d="M28 0H0V26H28M0 13H21"/></g></g></svg>
|
|
22
|
+
</span>
|
|
23
|
+
<span id="salas-mac" class="salas-mac"></span>
|
|
24
|
+
<button id="salas-stop" class="salas-stop" type="button" hidden>
|
|
25
|
+
<svg viewBox="0 0 20 20" aria-hidden="true"><rect x="5" y="5" width="10" height="10" rx="2.6" fill="currentColor"/></svg>
|
|
26
|
+
<span>Stop all</span>
|
|
27
|
+
</button>
|
|
28
|
+
</header>
|
|
29
|
+
<main class="salas-main">
|
|
30
|
+
<button id="salas-continue" class="salas-continue" type="button" hidden></button>
|
|
31
|
+
<h1 class="salas-title">Home</h1>
|
|
32
|
+
<ul id="salas-list" class="salas-list" aria-live="polite"></ul>
|
|
33
|
+
<p id="salas-empty" class="salas-empty" hidden></p>
|
|
34
|
+
<p id="salas-foot" class="salas-foot"></p>
|
|
35
|
+
</main>
|
|
36
|
+
<div id="salas-welcome" class="salas-welcome" hidden role="dialog" aria-modal="true" aria-labelledby="salas-welcome-title">
|
|
37
|
+
<section class="salas-sheet">
|
|
38
|
+
<span class="salas-check" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M6 12.5l4 4 8-9" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
|
|
39
|
+
<h2 id="salas-welcome-title"></h2>
|
|
40
|
+
<p id="salas-welcome-text"></p>
|
|
41
|
+
<ol id="salas-welcome-steps" class="salas-steps"></ol>
|
|
42
|
+
<button id="salas-welcome-ok" class="salas-primary" type="button"></button>
|
|
43
|
+
</section>
|
|
44
|
+
</div>
|
|
45
|
+
<script type="module" src="/bridge/salas.js"></script>
|
|
46
|
+
</body>
|
|
47
|
+
</html>
|
package/public/salas.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// ROOMS, on the phone: every room open on the Mac, in one list (docs/MOBILE.md, MOV-004).
|
|
2
|
+
//
|
|
3
|
+
// The bridge serves this page and answers what it asks: which rooms are open, what each one is
|
|
4
|
+
// doing, and where the person was on the Mac. A row opens its room; the room's own page takes it
|
|
5
|
+
// from there, behind the same address. STOP ALL halts every agent in every room, after a second
|
|
6
|
+
// touch, because a phone in a pocket touches things.
|
|
7
|
+
|
|
8
|
+
import { setLanguage, t } from './i18n.js';
|
|
9
|
+
import { dropStickyHover } from './touch.js';
|
|
10
|
+
|
|
11
|
+
// A tapped card does not stay lit on the phone (public/touch.js).
|
|
12
|
+
dropStickyHover();
|
|
13
|
+
|
|
14
|
+
const $ = (id) => document.getElementById(id);
|
|
15
|
+
const root = document.documentElement;
|
|
16
|
+
const ui = { list: $('salas-list'), empty: $('salas-empty'), foot: $('salas-foot'), mac: $('salas-mac'), stop: $('salas-stop'), carry: $('salas-continue'), welcome: $('salas-welcome') };
|
|
17
|
+
let language = 'es';
|
|
18
|
+
let timer = 0;
|
|
19
|
+
let arming = 0;
|
|
20
|
+
|
|
21
|
+
// The room's looks, from this browser: the same keys the room's own page keeps.
|
|
22
|
+
(() => {
|
|
23
|
+
let theme = 'auto';
|
|
24
|
+
let accent = null;
|
|
25
|
+
try { theme = localStorage.getItem('pulse.theme') ?? 'auto'; accent = localStorage.getItem('pulse.accent'); } catch { /* no storage */ }
|
|
26
|
+
const light = window.matchMedia?.('(prefers-color-scheme: light)').matches;
|
|
27
|
+
if (theme === 'light' || theme === 'dark') root.dataset.theme = theme;
|
|
28
|
+
root.dataset.scheme = theme === 'auto' ? (light ? 'light' : 'dark') : theme;
|
|
29
|
+
if (accent && accent !== 'madre') root.dataset.accent = accent;
|
|
30
|
+
})();
|
|
31
|
+
|
|
32
|
+
const el = (tag, className, text) => {
|
|
33
|
+
const node = document.createElement(tag);
|
|
34
|
+
if (className) node.className = className;
|
|
35
|
+
if (text !== undefined && text !== null) node.textContent = text;
|
|
36
|
+
return node;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const chatName = (title) => (title === 'First conversation' ? t('First conversation') : title === 'Untitled' ? t('Untitled') : title === 'Coordinator' ? t('Coordinator') : title);
|
|
40
|
+
|
|
41
|
+
function ago(iso) {
|
|
42
|
+
const at = Date.parse(iso ?? '');
|
|
43
|
+
if (!Number.isFinite(at)) return '';
|
|
44
|
+
const seconds = (at - Date.now()) / 1000;
|
|
45
|
+
const away = Math.abs(seconds);
|
|
46
|
+
const words = new Intl.RelativeTimeFormat(language, { numeric: 'auto', style: 'short' });
|
|
47
|
+
if (away < 60) return t('now');
|
|
48
|
+
if (away < 3600) return words.format(Math.round(seconds / 60), 'minute');
|
|
49
|
+
if (away < 86400) return words.format(Math.round(seconds / 3600), 'hour');
|
|
50
|
+
if (away < 604800) return words.format(Math.round(seconds / 86400), 'day');
|
|
51
|
+
return new Date(at).toLocaleDateString(language, { day: 'numeric', month: 'short' });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function openRoom(id, { carry = false, chat = null } = {}) {
|
|
55
|
+
const answer = await fetch('/bridge/open', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ room: id }) });
|
|
56
|
+
if (!answer.ok) { await refresh(); return; }
|
|
57
|
+
// A conversation by its own address: the phone never moves the one the room has open on the Mac.
|
|
58
|
+
location.href = carry ? '/?continuar=mac' : chat ? `/?en=${encodeURIComponent(chat)}` : '/';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// The modes as the room names them (app.js, MODES): asked when shown, so the language is the page's.
|
|
62
|
+
const modeName = (n) => [t('INCOGNITO'), t('CHAT ONLY'), t('CREATE'), t('EDIT'), t('FULL ACCESS')][n] ?? '';
|
|
63
|
+
// What an agent is waiting for, in one line.
|
|
64
|
+
function askLine(ask) {
|
|
65
|
+
if (ask.kind === 'mode') return t('@{agent} asks for #{n} {label}', { agent: ask.agent, n: ask.mode, label: modeName(ask.mode) });
|
|
66
|
+
if (ask.kind === 'run') return ask.text ? t('@{agent} wants to run {line}', { agent: ask.agent, line: ask.text }) : t('@{agent} wants to run a command', { agent: ask.agent });
|
|
67
|
+
if (ask.kind === 'bishop') return t('The Coordinator proposes #{n} to @{agent}: unlock it with your key', { n: ask.mode, agent: ask.agent });
|
|
68
|
+
if (ask.kind === 'plan') return ask.text ? t('The Coordinator waits for your press: {reason}', { reason: ask.text }) : t('The Coordinator waits for your press');
|
|
69
|
+
return ask.text ? t('@{agent} asks you: {question}', { agent: ask.agent, question: ask.text }) : t('@{agent} asks you to choose', { agent: ask.agent });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function drawRoom(room) {
|
|
73
|
+
const item = el('li');
|
|
74
|
+
const row = el('button', 'salas-room');
|
|
75
|
+
row.type = 'button';
|
|
76
|
+
const head = el('span', 'salas-room-head');
|
|
77
|
+
head.append(el('b', null, room.coordinator ? t('Coordinator') : room.project), el('time', null, ago(room.chat?.at)));
|
|
78
|
+
row.append(head);
|
|
79
|
+
if (room.waitingCount) row.classList.add('waiting');
|
|
80
|
+
if (room.outdated || room.unreachable) {
|
|
81
|
+
row.disabled = true;
|
|
82
|
+
row.append(el('span', 'salas-room-note', room.outdated ? t('Update MADRE in this project on your Mac to use it from here.') : t('This project is not answering.')));
|
|
83
|
+
} else {
|
|
84
|
+
const ask = room.waiting?.[0];
|
|
85
|
+
if (ask) {
|
|
86
|
+
// What waits comes first, in the accent: it is the one thing on this screen that needs you.
|
|
87
|
+
const mark = el('span', 'salas-room-wait');
|
|
88
|
+
mark.append(el('b', null, room.waitingCount > 1 ? t('Waiting for you · {n}', { n: room.waitingCount }) : t('Waiting for you')), el('span', null, askLine(ask)));
|
|
89
|
+
row.append(mark);
|
|
90
|
+
if (ask.chatTitle) row.append(el('span', 'salas-room-chat', chatName(ask.chatTitle)));
|
|
91
|
+
} else {
|
|
92
|
+
if (room.chat) row.append(el('span', 'salas-room-chat', chatName(room.chat.title)));
|
|
93
|
+
if (room.chat?.preview) row.append(el('span', 'salas-room-preview', room.chat.preview));
|
|
94
|
+
}
|
|
95
|
+
// The macroverse on a phone (docs/MACROVERSO.md): no map, the archive of each room as one line
|
|
96
|
+
// in NOSTROMO's colours, and how much it holds.
|
|
97
|
+
if (room.census?.memories) {
|
|
98
|
+
const strip = el('span', 'salas-room-strip');
|
|
99
|
+
for (const kind of ['decision', 'fact', 'preference', 'question', 'aberration']) {
|
|
100
|
+
const count = room.census.kinds?.[kind] ?? 0;
|
|
101
|
+
if (!count) continue;
|
|
102
|
+
const part = el('i', `k-${kind}`);
|
|
103
|
+
part.style.flexGrow = String(count);
|
|
104
|
+
strip.append(part);
|
|
105
|
+
}
|
|
106
|
+
row.append(strip, el('span', 'salas-room-count', t('{n} memories', { n: room.census.memories })));
|
|
107
|
+
}
|
|
108
|
+
if (room.working?.length) {
|
|
109
|
+
const busy = el('span', 'salas-room-busy');
|
|
110
|
+
busy.append(el('i', null), document.createTextNode(t('{agents} working', { agents: room.working.map((id) => `@${id}`).join(', ') })));
|
|
111
|
+
row.append(busy);
|
|
112
|
+
}
|
|
113
|
+
row.addEventListener('click', () => { void openRoom(room.id, { chat: ask?.chat ?? null }); });
|
|
114
|
+
}
|
|
115
|
+
item.append(row);
|
|
116
|
+
return item;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function drawCarry(entry, rooms) {
|
|
120
|
+
const open = entry && rooms.some((room) => room.id === entry.room && !room.outdated && !room.unreachable);
|
|
121
|
+
ui.carry.hidden = !open;
|
|
122
|
+
if (!open) return;
|
|
123
|
+
ui.carry.replaceChildren();
|
|
124
|
+
const words = el('span', 'salas-continue-words');
|
|
125
|
+
words.append(el('b', null, t('Pick up where you were')), el('span', null, [entry.project, entry.chatTitle ? chatName(entry.chatTitle) : null].filter(Boolean).join(' · ')));
|
|
126
|
+
if (entry.draft) words.append(el('span', 'salas-continue-draft', `“${entry.draft.slice(0, 80)}${entry.draft.length > 80 ? '…' : ''}”`));
|
|
127
|
+
ui.carry.append(words, el('span', 'salas-continue-go', '›'));
|
|
128
|
+
ui.carry.onclick = () => { void openRoom(entry.room, { carry: true }); };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function refresh() {
|
|
132
|
+
let data;
|
|
133
|
+
try {
|
|
134
|
+
const answer = await fetch('/bridge/rooms', { cache: 'no-store' });
|
|
135
|
+
if (answer.status === 401) { location.reload(); return; }
|
|
136
|
+
data = await answer.json();
|
|
137
|
+
} catch {
|
|
138
|
+
ui.foot.textContent = t('Your Mac is not answering. Check that it is awake and on the same Wi-Fi.');
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (data.language && data.language !== language) { language = setLanguage(data.language); root.lang = language; translateStatic(); }
|
|
142
|
+
ui.mac.textContent = t('On {mac}', { mac: data.mac });
|
|
143
|
+
// What waits for you first, then what is working, then the rest as the Mac lists them, and last
|
|
144
|
+
// the rooms this phone cannot use yet.
|
|
145
|
+
const weight = (room) => (room.outdated || room.unreachable ? -1 : (room.waitingCount ? 2 : 0) + (room.working?.length ? 1 : 0));
|
|
146
|
+
const ordered = [...data.rooms].sort((a, b) => weight(b) - weight(a));
|
|
147
|
+
ui.list.replaceChildren(...ordered.map(drawRoom));
|
|
148
|
+
ui.empty.hidden = data.rooms.length > 0;
|
|
149
|
+
ui.empty.textContent = t('No project is open on your Mac. Open MADRE in a project there and it appears here.');
|
|
150
|
+
ui.stop.hidden = !data.rooms.some((room) => room.working?.length);
|
|
151
|
+
ui.foot.textContent = t('Your projects run on your Mac. This phone reaches them over your Wi-Fi.');
|
|
152
|
+
drawCarry(data.handoff, data.rooms);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function translateStatic() {
|
|
156
|
+
document.querySelector('.salas-title').textContent = t('Home');
|
|
157
|
+
ui.stop.querySelector('span').textContent = arming ? t('Touch again to stop') : t('Stop all');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
ui.stop.addEventListener('click', async () => {
|
|
161
|
+
if (!arming) {
|
|
162
|
+
ui.stop.classList.add('armed');
|
|
163
|
+
arming = setTimeout(() => { arming = 0; ui.stop.classList.remove('armed'); translateStatic(); }, 3000);
|
|
164
|
+
translateStatic();
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
clearTimeout(arming);
|
|
168
|
+
arming = 0;
|
|
169
|
+
ui.stop.classList.remove('armed');
|
|
170
|
+
ui.stop.disabled = true;
|
|
171
|
+
try { await fetch('/bridge/stop-all', { method: 'POST', headers: { 'content-type': 'application/json' }, body: '{}' }); } finally { ui.stop.disabled = false; translateStatic(); await refresh(); }
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Right after pairing: it worked, and how to keep MADRE on the home screen.
|
|
175
|
+
function welcome() {
|
|
176
|
+
if (!new URLSearchParams(location.search).has('listo')) return;
|
|
177
|
+
history.replaceState(null, '', '/salas');
|
|
178
|
+
const apple = /iPhone|iPad|Macintosh/i.test(navigator.userAgent);
|
|
179
|
+
$('salas-welcome-title').textContent = t('Done. Your Mac is on your phone.');
|
|
180
|
+
$('salas-welcome-text').textContent = t('To open MADRE like an app, add it to your home screen:');
|
|
181
|
+
const steps = apple
|
|
182
|
+
? [t('Touch Share, at the bottom of Safari.'), t('Choose «Add to Home Screen».')]
|
|
183
|
+
: [t('Touch the ⋮ menu of your browser.'), t('Choose «Add to Home screen».')];
|
|
184
|
+
$('salas-welcome-steps').replaceChildren(...steps.map((step) => el('li', null, step)));
|
|
185
|
+
$('salas-welcome-ok').textContent = t('Got it');
|
|
186
|
+
$('salas-welcome-ok').onclick = () => { ui.welcome.hidden = true; };
|
|
187
|
+
ui.welcome.hidden = false;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function tick() {
|
|
191
|
+
clearInterval(timer);
|
|
192
|
+
if (document.visibilityState !== 'visible') return;
|
|
193
|
+
timer = setInterval(() => { void refresh(); }, 4000);
|
|
194
|
+
}
|
|
195
|
+
document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible') void refresh(); tick(); });
|
|
196
|
+
|
|
197
|
+
translateStatic();
|
|
198
|
+
await refresh();
|
|
199
|
+
welcome();
|
|
200
|
+
tick();
|