Archived chats: a page to find them again, Restore chat, delete
ci / ui (push) Waiting to run
ci / check (push) Failing after 3s
ci / start (push) Failing after 3s

Archive was one-way in the app. Archived (sidebar, under Files) reads
GET /api/v2/chats?archived=true, newest first and paged; a row opens the
chat, Restore chat PATCHes archived:false and reads the sidebar again, and
the row's menu deletes. An open archived chat says so in its header, with
Restore chat there and in its menu. The words are the web's own ("Archived",
"Restore chat"). Tests: D2 at 1280 and 390 px.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
HomerandClaude Opus 5.5 committed 2026-11-10 12:19:35 +00:00
1 parent cf13d49ed6
commit 94e903ef70
10 files changed
+323 -3

No files matched your search

+9
View File
@@ -4,6 +4,15 @@ All notable changes to LLeMbas Desktop. The format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project uses [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project uses
[Semantic Versioning](https://semver.org/). [Semantic Versioning](https://semver.org/).
## [Unreleased]
### Added
- **Archived chats.** Archive chat was one-way: an archived chat left the sidebar and could not be
found again in the app. Archived, under Files in the sidebar, lists them newest first; each opens
as any chat does, goes back into the sidebar with Restore chat, or is deleted. An archived chat
that is open says so in its header, with Restore chat beside it and in its menu.
## [1.0.0-beta.6] — 2026-11-10 ## [1.0.0-beta.6] — 2026-11-10
LLeMbas CLI 2.0.0-beta.6 inside, the look and words of LLeMbas 2.0.0-beta.8. LLeMbas CLI 2.0.0-beta.6 inside, the look and words of LLeMbas 2.0.0-beta.8.
+12
View File
@@ -445,6 +445,10 @@ KEYS = (
"Stop %(name)s and the subagents under it?", "Stop %(name)s and the subagents under it?",
"This device is not linked right now.", "This device is not linked right now.",
"Subagent %(name)s (%(id)s), %(position)s, asks:", "Subagent %(name)s (%(id)s), %(position)s, asks:",
# archived.js
"Archived",
"Restore chat",
"No archived chats.",
) )
# The app's own sentences. The mode names (Manual, Edit, Auto, Plan) stay English, as the # The app's own sentences. The mode names (Manual, Edit, Auto, Plan) stay English, as the
@@ -673,6 +677,7 @@ OWN: dict[str, dict[str, str]] = {
"Days": "Dny", "Days": "Dny",
"Time": "Čas", "Time": "Čas",
"No day ticked: every day.": "Žádný den nezaškrtnutý: každý den.", "No day ticked: every day.": "Žádný den nezaškrtnutý: každý den.",
"No archived chats.": "Žádné archivované konverzace.",
}, },
"de": { "de": {
"Sign in again: the server no longer accepts this app's sign-in.": "Melden Sie sich erneut an: Der Server nimmt die Anmeldung dieser App nicht mehr an.", "Sign in again: the server no longer accepts this app's sign-in.": "Melden Sie sich erneut an: Der Server nimmt die Anmeldung dieser App nicht mehr an.",
@@ -896,6 +901,7 @@ OWN: dict[str, dict[str, str]] = {
"Days": "Tage", "Days": "Tage",
"Time": "Uhrzeit", "Time": "Uhrzeit",
"No day ticked: every day.": "Kein Tag angehakt: jeden Tag.", "No day ticked: every day.": "Kein Tag angehakt: jeden Tag.",
"No archived chats.": "Keine archivierten Chats.",
}, },
"es": { "es": {
"Sign in again: the server no longer accepts this app's sign-in.": "Vuelva a iniciar sesión: el servidor ya no acepta el inicio de sesión de esta aplicación.", "Sign in again: the server no longer accepts this app's sign-in.": "Vuelva a iniciar sesión: el servidor ya no acepta el inicio de sesión de esta aplicación.",
@@ -1119,6 +1125,7 @@ OWN: dict[str, dict[str, str]] = {
"Days": "Días", "Days": "Días",
"Time": "Hora", "Time": "Hora",
"No day ticked: every day.": "Ningún día marcado: todos los días.", "No day ticked: every day.": "Ningún día marcado: todos los días.",
"No archived chats.": "No hay chats archivados.",
}, },
"fr": { "fr": {
"Sign in again: the server no longer accepts this app's sign-in.": "Reconnectez-vous : le serveur n’accepte plus la connexion de cette application.", "Sign in again: the server no longer accepts this app's sign-in.": "Reconnectez-vous : le serveur n’accepte plus la connexion de cette application.",
@@ -1342,6 +1349,7 @@ OWN: dict[str, dict[str, str]] = {
"Days": "Jours", "Days": "Jours",
"Time": "Heure", "Time": "Heure",
"No day ticked: every day.": "Aucun jour coché : tous les jours.", "No day ticked: every day.": "Aucun jour coché : tous les jours.",
"No archived chats.": "Aucune conversation archivée.",
}, },
"hu": { "hu": {
"Sign in again: the server no longer accepts this app's sign-in.": "Jelentkezzen be újra: a szerver már nem fogadja el ennek az alkalmazásnak a bejelentkezését.", "Sign in again: the server no longer accepts this app's sign-in.": "Jelentkezzen be újra: a szerver már nem fogadja el ennek az alkalmazásnak a bejelentkezését.",
@@ -1565,6 +1573,7 @@ OWN: dict[str, dict[str, str]] = {
"Days": "Napok", "Days": "Napok",
"Time": "Időpont", "Time": "Időpont",
"No day ticked: every day.": "Ha egy nap sincs bejelölve: minden nap.", "No day ticked: every day.": "Ha egy nap sincs bejelölve: minden nap.",
"No archived chats.": "Nincs archivált beszélgetés.",
}, },
"it": { "it": {
"Sign in again: the server no longer accepts this app's sign-in.": "Accedi di nuovo: il server non accetta più l’accesso di questa app.", "Sign in again: the server no longer accepts this app's sign-in.": "Accedi di nuovo: il server non accetta più l’accesso di questa app.",
@@ -1788,6 +1797,7 @@ OWN: dict[str, dict[str, str]] = {
"Days": "Giorni", "Days": "Giorni",
"Time": "Ora", "Time": "Ora",
"No day ticked: every day.": "Nessun giorno spuntato: ogni giorno.", "No day ticked: every day.": "Nessun giorno spuntato: ogni giorno.",
"No archived chats.": "Nessuna chat archiviata.",
}, },
"pl": { "pl": {
"Sign in again: the server no longer accepts this app's sign-in.": "Zaloguj się ponownie: serwer nie przyjmuje już logowania tej aplikacji.", "Sign in again: the server no longer accepts this app's sign-in.": "Zaloguj się ponownie: serwer nie przyjmuje już logowania tej aplikacji.",
@@ -2011,6 +2021,7 @@ OWN: dict[str, dict[str, str]] = {
"Days": "Dni", "Days": "Dni",
"Time": "Godzina", "Time": "Godzina",
"No day ticked: every day.": "Żaden dzień niezaznaczony: codziennie.", "No day ticked: every day.": "Żaden dzień niezaznaczony: codziennie.",
"No archived chats.": "Brak zarchiwizowanych czatów.",
}, },
"sk": { "sk": {
"Sign in again: the server no longer accepts this app's sign-in.": "Prihláste sa znova: server už neprijíma prihlásenie tejto aplikácie.", "Sign in again: the server no longer accepts this app's sign-in.": "Prihláste sa znova: server už neprijíma prihlásenie tejto aplikácie.",
@@ -2234,6 +2245,7 @@ OWN: dict[str, dict[str, str]] = {
"Days": "Dni", "Days": "Dni",
"Time": "Čas", "Time": "Čas",
"No day ticked: every day.": "Žiadny deň nezaškrtnutý: každý deň.", "No day ticked: every day.": "Žiadny deň nezaškrtnutý: každý deň.",
"No archived chats.": "Žiadne archivované konverzácie.",
}, },
} }
+65
View File
@@ -822,6 +822,71 @@ test("C4: the tree at 390 px stays inside the window", async () => {
assert.deepEqual(p.errors, []) assert.deepEqual(p.errors, [])
}) })
// --- D: parity (beta.9) ------------------------------------------------------------------------
const shot = (p, name) => (process.env.SHOTS ? p.screenshot({ path: join(process.env.SHOTS, `${name}.png`) }) : null)
const noSideways = (p, width) => p.evaluate((w) => document.documentElement.scrollWidth <= w, width)
function archivedRoutes(over = {}) {
return {
status: status(),
routes: baseRoutes({
"GET /chats?archived=true&limit=50": {
chats: [
chatRow({ id: "old1", title: "Lembas for the road, a recipe with a title long enough to be cut short", archived: true }),
agentChat({ id: "old2", title: "Old refactor", archived: true, running: false }),
],
more: false,
},
"PATCH /chats/old1": { chat: chatRow({ id: "old1", title: "Lembas for the road", archived: false }) },
"GET /chats/old2": { chat: agentChat({ id: "old2", title: "Old refactor", archived: true, running: false }), messages: [userMessage("m9", "Tidy it")], more: false, live: null },
"PATCH /chats/old2": { chat: agentChat({ id: "old2", title: "Old refactor", archived: false, running: false }) },
...over,
}),
streams: {},
}
}
test("D2: Archived lists the archived chats; Restore chat takes one back and the sidebar is read again", async () => {
const p = await page(archivedRoutes())
await p.click("#nav-archived")
await p.waitForSelector("#page-archived:not([hidden]) .arow")
assert.equal(await p.locator("#page-archived .arow").count(), 2)
assert.match(await p.textContent('.arow[data-id="old2"]'), /project/, "an agent chat names its folder")
await shot(p, "d2-archived-1280")
const before = await p.evaluate(() => window.__mock.api("GET", "/chats?limit").length)
await p.click('.arow[data-id="old1"] .arow__unarchive')
await p.waitForFunction(() => window.__mock.api("PATCH", "/chats/old1").length === 1)
const [call] = await p.evaluate(() => window.__mock.api("PATCH", "/chats/old1"))
assert.deepEqual(call.args.body, { archived: false })
await p.waitForFunction(() => !document.querySelector('.arow[data-id="old1"]'))
await p.waitForFunction((n) => window.__mock.api("GET", "/chats?limit").length > n, before)
assert.deepEqual(p.errors, [])
})
test("D2: an archived chat opens from the page and says so in its header; Restore chat there too", async () => {
const p = await page(archivedRoutes())
await p.click("#nav-archived")
await p.waitForSelector('.arow[data-id="old2"] .arow__open')
await p.click('.arow[data-id="old2"] .arow__open')
await p.waitForSelector("#main:not([hidden]) #messages .msg")
await p.click("#chat-tools .head__unarchive")
await p.waitForFunction(() => window.__mock.api("PATCH", "/chats/old2").length === 1)
await p.waitForSelector("#chat-tools .head__unarchive", { state: "detached" })
assert.deepEqual(p.errors, [])
})
test("D2: at 390 px the archived page stays inside the window, its buttons reachable", async () => {
const p = await page(archivedRoutes(), { width: 390, height: 844 })
await p.evaluate(() => document.getElementById("nav-archived").click())
await p.waitForSelector("#page-archived:not([hidden]) .arow")
assert.ok(await noSideways(p, 390), "nothing scrolls sideways")
const b = await p.locator('.arow[data-id="old1"] .arow__unarchive').boundingBox()
assert.ok(b && b.x + b.width <= 390, `Unarchive is on the screen: ${JSON.stringify(b)}`)
await shot(p, "d2-archived-390")
assert.deepEqual(p.errors, [])
})
// --- Run ---------------------------------------------------------------------------------------- // --- Run ----------------------------------------------------------------------------------------
const only = process.argv[2] || "" const only = process.argv[2] || ""
+34
View File
@@ -1629,6 +1629,40 @@ details[open] > summary .step__chev .icon { transform: rotate(90deg); }
.findings__item { min-width: 0; overflow-wrap: anywhere; } .findings__item { min-width: 0; overflow-wrap: anywhere; }
.ask__subagent { color: var(--ink); } .ask__subagent { color: var(--ink); }
/* --- Archived chats: icon · title over its folder and date · Unarchive · more, on known tracks. */
.arch { list-style: none; margin: 0; padding: 0; display: grid; }
.arow {
display: grid;
grid-template-columns: 16px minmax(0, 1fr) auto var(--control-h-sm);
gap: var(--sp-3);
align-items: center;
min-height: var(--control-h);
padding: var(--sp-1) 0;
border-bottom: var(--border-w) solid var(--border);
}
.arow__icon { display: grid; place-items: center; color: var(--ink-faint); }
.arow__icon.is-agent { color: var(--leaf); }
.arow__icon .icon { width: 14px; height: 14px; }
.arow__open {
display: grid;
min-width: 0;
min-height: var(--control-h);
padding: var(--sp-1) 0;
text-align: left;
color: var(--ink);
background: transparent;
border: 0;
cursor: pointer;
}
.arow__open:hover .arow__title { text-decoration: underline; }
.arow__title, .arow__sub { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.arow__title { font: var(--text-sm) / var(--leading-tight) var(--font-body); }
.arow__sub { font: var(--text-xs) var(--font-ui); color: var(--ink-faint); }
.arch__more { margin-top: var(--sp-3); }
.arch__page { display: grid; gap: var(--sp-3); }
.head__unarchive { white-space: nowrap; }
/* --- Narrower windows ---------------------------------------------------------------------- */ /* --- Narrower windows ---------------------------------------------------------------------- */
/* 64rem (--bp-wide): a panel goes over the chat instead of beside it. */ /* 64rem (--bp-wide): a panel goes over the chat instead of beside it. */
+24
View File
@@ -55,6 +55,7 @@ const LEMBAS_I18N = {
"Answer": "Odpovědět", "Answer": "Odpovědět",
"Approve this code in the page that opened in your browser:": "Potvrďte tento kód na stránce, která se otevřela ve vašem prohlížeči:", "Approve this code in the page that opened in your browser:": "Potvrďte tento kód na stránce, která se otevřela ve vašem prohlížeči:",
"Archive chat": "Archivovat konverzaci", "Archive chat": "Archivovat konverzaci",
"Archived": "Archivované",
"Attach files": "Přikládat soubory", "Attach files": "Přikládat soubory",
"Auto — asks nothing": "Auto — na nic se neptá", "Auto — asks nothing": "Auto — na nic se neptá",
"Automatic compaction is off.": "Automatické shrnutí je vypnuté.", "Automatic compaction is off.": "Automatické shrnutí je vypnuté.",
@@ -168,6 +169,7 @@ const LEMBAS_I18N = {
"New chat": "Nová konverzace", "New chat": "Nová konverzace",
"New scheduled task": "Nová naplánovaná úloha", "New scheduled task": "Nová naplánovaná úloha",
"Next run": "Další spuštění", "Next run": "Další spuštění",
"No archived chats.": "Žádné archivované konverzace.",
"No chat yet. Start one with New chat.": "Zatím žádná konverzace. Začněte ji tlačítkem Nová konverzace.", "No chat yet. Start one with New chat.": "Zatím žádná konverzace. Začněte ji tlačítkem Nová konverzace.",
"No day ticked: every day.": "Žádný den nezaškrtnutý: každý den.", "No day ticked: every day.": "Žádný den nezaškrtnutý: každý den.",
"No files written in this chat yet.": "V tomto chatu se zatím nezapsal žádný soubor.", "No files written in this chat yet.": "V tomto chatu se zatím nezapsal žádný soubor.",
@@ -226,6 +228,7 @@ const LEMBAS_I18N = {
"Rename chat": "Přejmenovat konverzaci", "Rename chat": "Přejmenovat konverzaci",
"Rename…": "Přejmenovat…", "Rename…": "Přejmenovat…",
"Restore": "Obnovit", "Restore": "Obnovit",
"Restore chat": "Obnovit konverzaci",
"Result": "Výsledek", "Result": "Výsledek",
"Resume": "Pokračovat", "Resume": "Pokračovat",
"Resume %(title)s": "Obnovit %(title)s", "Resume %(title)s": "Obnovit %(title)s",
@@ -452,6 +455,7 @@ const LEMBAS_I18N = {
"Answer": "Antworten", "Answer": "Antworten",
"Approve this code in the page that opened in your browser:": "Bestätigen Sie diesen Code auf der Seite, die sich in Ihrem Browser geöffnet hat:", "Approve this code in the page that opened in your browser:": "Bestätigen Sie diesen Code auf der Seite, die sich in Ihrem Browser geöffnet hat:",
"Archive chat": "Chat archivieren", "Archive chat": "Chat archivieren",
"Archived": "Archiviert",
"Attach files": "Dateien anhängen", "Attach files": "Dateien anhängen",
"Auto — asks nothing": "Auto — fragt nichts", "Auto — asks nothing": "Auto — fragt nichts",
"Automatic compaction is off.": "Die automatische Zusammenfassung ist aus.", "Automatic compaction is off.": "Die automatische Zusammenfassung ist aus.",
@@ -565,6 +569,7 @@ const LEMBAS_I18N = {
"New chat": "Neuer Chat", "New chat": "Neuer Chat",
"New scheduled task": "Neue geplante Aufgabe", "New scheduled task": "Neue geplante Aufgabe",
"Next run": "Nächster Lauf", "Next run": "Nächster Lauf",
"No archived chats.": "Keine archivierten Chats.",
"No chat yet. Start one with New chat.": "Noch kein Chat. Beginnen Sie einen mit Neuer Chat.", "No chat yet. Start one with New chat.": "Noch kein Chat. Beginnen Sie einen mit Neuer Chat.",
"No day ticked: every day.": "Kein Tag angehakt: jeden Tag.", "No day ticked: every day.": "Kein Tag angehakt: jeden Tag.",
"No files written in this chat yet.": "In diesem Chat wurde noch keine Datei geschrieben.", "No files written in this chat yet.": "In diesem Chat wurde noch keine Datei geschrieben.",
@@ -623,6 +628,7 @@ const LEMBAS_I18N = {
"Rename chat": "Chat umbenennen", "Rename chat": "Chat umbenennen",
"Rename…": "Umbenennen…", "Rename…": "Umbenennen…",
"Restore": "Wiederherstellen", "Restore": "Wiederherstellen",
"Restore chat": "Chat wiederherstellen",
"Result": "Ergebnis", "Result": "Ergebnis",
"Resume": "Fortsetzen", "Resume": "Fortsetzen",
"Resume %(title)s": "%(title)s fortsetzen", "Resume %(title)s": "%(title)s fortsetzen",
@@ -849,6 +855,7 @@ const LEMBAS_I18N = {
"Answer": "Responder", "Answer": "Responder",
"Approve this code in the page that opened in your browser:": "Apruebe este código en la página que se abrió en su navegador:", "Approve this code in the page that opened in your browser:": "Apruebe este código en la página que se abrió en su navegador:",
"Archive chat": "Archivar chat", "Archive chat": "Archivar chat",
"Archived": "Archivados",
"Attach files": "Adjuntar archivos", "Attach files": "Adjuntar archivos",
"Auto — asks nothing": "Auto — no pregunta nada", "Auto — asks nothing": "Auto — no pregunta nada",
"Automatic compaction is off.": "La compactación automática está desactivada.", "Automatic compaction is off.": "La compactación automática está desactivada.",
@@ -962,6 +969,7 @@ const LEMBAS_I18N = {
"New chat": "Nuevo chat", "New chat": "Nuevo chat",
"New scheduled task": "Nueva tarea programada", "New scheduled task": "Nueva tarea programada",
"Next run": "Próxima ejecución", "Next run": "Próxima ejecución",
"No archived chats.": "No hay chats archivados.",
"No chat yet. Start one with New chat.": "Aún no hay chats. Empiece uno con Nuevo chat.", "No chat yet. Start one with New chat.": "Aún no hay chats. Empiece uno con Nuevo chat.",
"No day ticked: every day.": "Ningún día marcado: todos los días.", "No day ticked: every day.": "Ningún día marcado: todos los días.",
"No files written in this chat yet.": "Todavía no se ha escrito ningún archivo en este chat.", "No files written in this chat yet.": "Todavía no se ha escrito ningún archivo en este chat.",
@@ -1020,6 +1028,7 @@ const LEMBAS_I18N = {
"Rename chat": "Renombrar chat", "Rename chat": "Renombrar chat",
"Rename…": "Renombrar…", "Rename…": "Renombrar…",
"Restore": "Restaurar", "Restore": "Restaurar",
"Restore chat": "Restaurar chat",
"Result": "Resultado", "Result": "Resultado",
"Resume": "Reanudar", "Resume": "Reanudar",
"Resume %(title)s": "Reanudar %(title)s", "Resume %(title)s": "Reanudar %(title)s",
@@ -1246,6 +1255,7 @@ const LEMBAS_I18N = {
"Answer": "Répondre", "Answer": "Répondre",
"Approve this code in the page that opened in your browser:": "Approuvez ce code sur la page qui s'est ouverte dans votre navigateur :", "Approve this code in the page that opened in your browser:": "Approuvez ce code sur la page qui s'est ouverte dans votre navigateur :",
"Archive chat": "Archiver la conversation", "Archive chat": "Archiver la conversation",
"Archived": "Archivées",
"Attach files": "Joindre des fichiers", "Attach files": "Joindre des fichiers",
"Auto — asks nothing": "Auto — ne demande rien", "Auto — asks nothing": "Auto — ne demande rien",
"Automatic compaction is off.": "Le compactage automatique est désactivé.", "Automatic compaction is off.": "Le compactage automatique est désactivé.",
@@ -1359,6 +1369,7 @@ const LEMBAS_I18N = {
"New chat": "Nouvelle conversation", "New chat": "Nouvelle conversation",
"New scheduled task": "Nouvelle tâche planifiée", "New scheduled task": "Nouvelle tâche planifiée",
"Next run": "Prochaine exécution", "Next run": "Prochaine exécution",
"No archived chats.": "Aucune conversation archivée.",
"No chat yet. Start one with New chat.": "Aucune conversation pour l’instant. Commencez-en une avec Nouvelle conversation.", "No chat yet. Start one with New chat.": "Aucune conversation pour l’instant. Commencez-en une avec Nouvelle conversation.",
"No day ticked: every day.": "Aucun jour coché : tous les jours.", "No day ticked: every day.": "Aucun jour coché : tous les jours.",
"No files written in this chat yet.": "Aucun fichier n’a encore été écrit dans cette conversation.", "No files written in this chat yet.": "Aucun fichier n’a encore été écrit dans cette conversation.",
@@ -1417,6 +1428,7 @@ const LEMBAS_I18N = {
"Rename chat": "Renommer la conversation", "Rename chat": "Renommer la conversation",
"Rename…": "Renommer…", "Rename…": "Renommer…",
"Restore": "Restaurer", "Restore": "Restaurer",
"Restore chat": "Restaurer la conversation",
"Result": "Résultat", "Result": "Résultat",
"Resume": "Reprendre", "Resume": "Reprendre",
"Resume %(title)s": "Reprendre %(title)s", "Resume %(title)s": "Reprendre %(title)s",
@@ -1643,6 +1655,7 @@ const LEMBAS_I18N = {
"Answer": "Válasz", "Answer": "Válasz",
"Approve this code in the page that opened in your browser:": "Hagyja jóvá ezt a kódot a böngészőjében megnyílt oldalon:", "Approve this code in the page that opened in your browser:": "Hagyja jóvá ezt a kódot a böngészőjében megnyílt oldalon:",
"Archive chat": "Csevegés archiválása", "Archive chat": "Csevegés archiválása",
"Archived": "Archivált",
"Attach files": "Fájlok csatolása", "Attach files": "Fájlok csatolása",
"Auto — asks nothing": "Auto — semmit nem kérdez", "Auto — asks nothing": "Auto — semmit nem kérdez",
"Automatic compaction is off.": "Az automatikus tömörítés ki van kapcsolva.", "Automatic compaction is off.": "Az automatikus tömörítés ki van kapcsolva.",
@@ -1756,6 +1769,7 @@ const LEMBAS_I18N = {
"New chat": "Új csevegés", "New chat": "Új csevegés",
"New scheduled task": "Új ütemezett feladat", "New scheduled task": "Új ütemezett feladat",
"Next run": "Következő futás", "Next run": "Következő futás",
"No archived chats.": "Nincs archivált beszélgetés.",
"No chat yet. Start one with New chat.": "Még nincs beszélgetés. Indítson egyet az Új beszélgetés gombbal.", "No chat yet. Start one with New chat.": "Még nincs beszélgetés. Indítson egyet az Új beszélgetés gombbal.",
"No day ticked: every day.": "Ha egy nap sincs bejelölve: minden nap.", "No day ticked: every day.": "Ha egy nap sincs bejelölve: minden nap.",
"No files written in this chat yet.": "Ebben a csevegésben még nem íródott fájl.", "No files written in this chat yet.": "Ebben a csevegésben még nem íródott fájl.",
@@ -1814,6 +1828,7 @@ const LEMBAS_I18N = {
"Rename chat": "Csevegés átnevezése", "Rename chat": "Csevegés átnevezése",
"Rename…": "Átnevezés…", "Rename…": "Átnevezés…",
"Restore": "Visszaállítás", "Restore": "Visszaállítás",
"Restore chat": "Csevegés visszaállítása",
"Result": "Eredmény", "Result": "Eredmény",
"Resume": "Folytatás", "Resume": "Folytatás",
"Resume %(title)s": "%(title)s folytatása", "Resume %(title)s": "%(title)s folytatása",
@@ -2040,6 +2055,7 @@ const LEMBAS_I18N = {
"Answer": "Rispondi", "Answer": "Rispondi",
"Approve this code in the page that opened in your browser:": "Approvi questo codice nella pagina che si è aperta nel suo browser:", "Approve this code in the page that opened in your browser:": "Approvi questo codice nella pagina che si è aperta nel suo browser:",
"Archive chat": "Archivia chat", "Archive chat": "Archivia chat",
"Archived": "Archiviate",
"Attach files": "Allegare file", "Attach files": "Allegare file",
"Auto — asks nothing": "Auto — non chiede nulla", "Auto — asks nothing": "Auto — non chiede nulla",
"Automatic compaction is off.": "La compattazione automatica è disattivata.", "Automatic compaction is off.": "La compattazione automatica è disattivata.",
@@ -2153,6 +2169,7 @@ const LEMBAS_I18N = {
"New chat": "Nuova chat", "New chat": "Nuova chat",
"New scheduled task": "Nuova attività pianificata", "New scheduled task": "Nuova attività pianificata",
"Next run": "Prossima esecuzione", "Next run": "Prossima esecuzione",
"No archived chats.": "Nessuna chat archiviata.",
"No chat yet. Start one with New chat.": "Ancora nessuna chat. Iniziane una con Nuova chat.", "No chat yet. Start one with New chat.": "Ancora nessuna chat. Iniziane una con Nuova chat.",
"No day ticked: every day.": "Nessun giorno spuntato: ogni giorno.", "No day ticked: every day.": "Nessun giorno spuntato: ogni giorno.",
"No files written in this chat yet.": "In questa chat non è ancora stato scritto alcun file.", "No files written in this chat yet.": "In questa chat non è ancora stato scritto alcun file.",
@@ -2211,6 +2228,7 @@ const LEMBAS_I18N = {
"Rename chat": "Rinomina chat", "Rename chat": "Rinomina chat",
"Rename…": "Rinomina…", "Rename…": "Rinomina…",
"Restore": "Ripristina", "Restore": "Ripristina",
"Restore chat": "Ripristina chat",
"Result": "Risultato", "Result": "Risultato",
"Resume": "Riprendi", "Resume": "Riprendi",
"Resume %(title)s": "Riprendi %(title)s", "Resume %(title)s": "Riprendi %(title)s",
@@ -2437,6 +2455,7 @@ const LEMBAS_I18N = {
"Answer": "Odpowiedź", "Answer": "Odpowiedź",
"Approve this code in the page that opened in your browser:": "Zatwierdź ten kod na stronie, która otworzyła się w przeglądarce:", "Approve this code in the page that opened in your browser:": "Zatwierdź ten kod na stronie, która otworzyła się w przeglądarce:",
"Archive chat": "Archiwizuj czat", "Archive chat": "Archiwizuj czat",
"Archived": "Zarchiwizowane",
"Attach files": "Załączaj pliki", "Attach files": "Załączaj pliki",
"Auto — asks nothing": "Auto — o nic nie pyta", "Auto — asks nothing": "Auto — o nic nie pyta",
"Automatic compaction is off.": "Automatyczne streszczanie jest wyłączone.", "Automatic compaction is off.": "Automatyczne streszczanie jest wyłączone.",
@@ -2550,6 +2569,7 @@ const LEMBAS_I18N = {
"New chat": "Nowy czat", "New chat": "Nowy czat",
"New scheduled task": "Nowe zaplanowane zadanie", "New scheduled task": "Nowe zaplanowane zadanie",
"Next run": "Następne uruchomienie", "Next run": "Następne uruchomienie",
"No archived chats.": "Brak zarchiwizowanych czatów.",
"No chat yet. Start one with New chat.": "Jeszcze nie ma czatu. Zacznij go przyciskiem Nowy czat.", "No chat yet. Start one with New chat.": "Jeszcze nie ma czatu. Zacznij go przyciskiem Nowy czat.",
"No day ticked: every day.": "Żaden dzień niezaznaczony: codziennie.", "No day ticked: every day.": "Żaden dzień niezaznaczony: codziennie.",
"No files written in this chat yet.": "W tym czacie nie zapisano jeszcze żadnego pliku.", "No files written in this chat yet.": "W tym czacie nie zapisano jeszcze żadnego pliku.",
@@ -2608,6 +2628,7 @@ const LEMBAS_I18N = {
"Rename chat": "Zmień nazwę czatu", "Rename chat": "Zmień nazwę czatu",
"Rename…": "Zmień nazwę…", "Rename…": "Zmień nazwę…",
"Restore": "Przywróć", "Restore": "Przywróć",
"Restore chat": "Przywróć czat",
"Result": "Wynik", "Result": "Wynik",
"Resume": "Wznów", "Resume": "Wznów",
"Resume %(title)s": "Wznów %(title)s", "Resume %(title)s": "Wznów %(title)s",
@@ -2834,6 +2855,7 @@ const LEMBAS_I18N = {
"Answer": "Odpovedať", "Answer": "Odpovedať",
"Approve this code in the page that opened in your browser:": "Potvrďte tento kód na stránke, ktorá sa otvorila vo vašom prehliadači:", "Approve this code in the page that opened in your browser:": "Potvrďte tento kód na stránke, ktorá sa otvorila vo vašom prehliadači:",
"Archive chat": "Archivovať konverzáciu", "Archive chat": "Archivovať konverzáciu",
"Archived": "Archivované",
"Attach files": "Prikladať súbory", "Attach files": "Prikladať súbory",
"Auto — asks nothing": "Auto — na nič sa nepýta", "Auto — asks nothing": "Auto — na nič sa nepýta",
"Automatic compaction is off.": "Automatické zhrnutie je vypnuté.", "Automatic compaction is off.": "Automatické zhrnutie je vypnuté.",
@@ -2947,6 +2969,7 @@ const LEMBAS_I18N = {
"New chat": "Nová konverzácia", "New chat": "Nová konverzácia",
"New scheduled task": "Nová naplánovaná úloha", "New scheduled task": "Nová naplánovaná úloha",
"Next run": "Ďalšie spustenie", "Next run": "Ďalšie spustenie",
"No archived chats.": "Žiadne archivované konverzácie.",
"No chat yet. Start one with New chat.": "Zatiaľ žiadna konverzácia. Začnite ju tlačidlom Nová konverzácia.", "No chat yet. Start one with New chat.": "Zatiaľ žiadna konverzácia. Začnite ju tlačidlom Nová konverzácia.",
"No day ticked: every day.": "Žiadny deň nezaškrtnutý: každý deň.", "No day ticked: every day.": "Žiadny deň nezaškrtnutý: každý deň.",
"No files written in this chat yet.": "V tomto chate sa zatiaľ nezapísal žiadny súbor.", "No files written in this chat yet.": "V tomto chate sa zatiaľ nezapísal žiadny súbor.",
@@ -3005,6 +3028,7 @@ const LEMBAS_I18N = {
"Rename chat": "Premenovať konverzáciu", "Rename chat": "Premenovať konverzáciu",
"Rename…": "Premenovať…", "Rename…": "Premenovať…",
"Restore": "Obnoviť", "Restore": "Obnoviť",
"Restore chat": "Obnoviť konverzáciu",
"Result": "Výsledok", "Result": "Výsledok",
"Resume": "Pokračovať", "Resume": "Pokračovať",
"Resume %(title)s": "Obnoviť %(title)s", "Resume %(title)s": "Obnoviť %(title)s",
+10
View File
@@ -53,6 +53,7 @@
<div class="side__nav"> <div class="side__nav">
<button type="button" class="navrow" id="nav-scheduled" data-view="scheduled"><span class="navrow__icon"></span><span data-i18n>Scheduled</span></button> <button type="button" class="navrow" id="nav-scheduled" data-view="scheduled"><span class="navrow__icon"></span><span data-i18n>Scheduled</span></button>
<button type="button" class="navrow" id="nav-files" data-view="files"><span class="navrow__icon"></span><span data-i18n>Files</span></button> <button type="button" class="navrow" id="nav-files" data-view="files"><span class="navrow__icon"></span><span data-i18n>Files</span></button>
<button type="button" class="navrow" id="nav-archived" data-view="archived"><span class="navrow__icon"></span><span data-i18n>Archived</span></button>
</div> </div>
<ul class="list" id="chat-list" aria-label="Chats" data-i18n-aria-label></ul> <ul class="list" id="chat-list" aria-label="Chats" data-i18n-aria-label></ul>
<div class="side__foot"> <div class="side__foot">
@@ -123,6 +124,15 @@
<div class="page__body" id="scheduled-body"></div> <div class="page__body" id="scheduled-body"></div>
</section> </section>
<section class="page" id="page-archived" hidden aria-labelledby="archived-title">
<header class="head">
<h1 class="head__title" id="archived-title" data-i18n>Archived</h1>
<div class="head__place"></div>
<div class="head__tools"><button type="button" class="tool" id="archived-refresh" aria-label="Read again" title="Read again" data-i18n-aria-label data-i18n-title></button></div>
</header>
<div class="page__body" id="archived-body"></div>
</section>
<section class="page settings" id="settings" hidden aria-labelledby="settings-title"> <section class="page settings" id="settings" hidden aria-labelledby="settings-title">
<header class="head"> <header class="head">
<h1 class="head__title" id="settings-title" data-i18n>Settings</h1> <h1 class="head__title" id="settings-title" data-i18n>Settings</h1>
+135
View File
@@ -0,0 +1,135 @@
// Archived chats: the ones put away from the sidebar with Archive chat, newest first. Each can be
// opened (read, and written to, as any chat), taken back into the sidebar (Restore chat), or deleted.
//
// GET /chats?archived=true&limit=&before= { chats: [chat], more }
// PATCH /chats/{id} { archived: false } { chat }
// DELETE /chats/{id} 204
//
// A chat's title is the person's or a model's words, set as text.
import { $, h, clear, icon, ICONS, when } from "./dom.js"
import { get, patch, del, why } from "./bridge.js"
import { ask, menu } from "./popup.js"
import { folderName } from "./list.js"
let ctx = null // { open(id), changed(), me() }
let rows = []
let more = false
let error = ""
let loading = false
/** The page, read again from the server. */
export async function showArchived() {
loading = true
error = ""
render()
try {
const r = await get("/chats?archived=true&limit=50")
rows = r.chats || []
more = !!r.more
} catch (err) {
rows = []
more = false
error = why(err)
}
loading = false
render()
}
async function loadMore(button) {
const last = rows[rows.length - 1]
if (!last) return
button.disabled = true
try {
const r = await get(`/chats?archived=true&limit=50&before=${encodeURIComponent(last.id)}`)
rows = rows.concat(r.chats || [])
more = !!r.more
} catch (err) {
error = why(err)
}
render()
}
/** Back into the sidebar. */
export async function unarchive(chat) {
try {
const r = await patch(`/chats/${encodeURIComponent(chat.id)}`, { archived: false })
rows = rows.filter((x) => x.id !== chat.id)
error = ""
render()
ctx?.changed(r?.chat || chat)
return true
} catch (err) {
error = why(err)
render()
return false
}
}
async function remove(chat) {
const yes = await ask({
title: t("Delete this chat?"),
text: t("“%(title)s” is deleted on the server, with its messages and files. This cannot be undone.", { title: chat.title || t("New chat") }),
ok: t("Delete"),
cancel: t("Cancel"),
danger: true,
})
if (!yes) return
try {
await del(`/chats/${encodeURIComponent(chat.id)}`)
rows = rows.filter((x) => x.id !== chat.id)
error = ""
} catch (err) {
error = why(err)
}
render()
}
function row(c) {
const agent = c.kind === "agent"
const title = c.title || t("New chat")
const mayDelete = ctx.me()?.permissions?.["chat.delete"] !== false
const sub = [agent && c.device?.directory ? folderName(c.device.directory) : "", when(c.updated_at)].filter(Boolean).join(" · ")
return h(
"li",
{ class: "arow", dataset: { id: c.id } },
h("span", { class: `arow__icon${agent ? " is-agent" : ""}` }, icon(agent ? ICONS.terminal : ICONS.chat)),
h(
"button",
{ type: "button", class: "arow__open", title, onclick: () => ctx.open(c.id) },
h("span", { class: "arow__title" }, h("bdi", { text: title })),
h("span", { class: "arow__sub", text: sub }),
),
h("button", { type: "button", class: "btn btn--sm arow__unarchive", text: t("Restore chat"), onclick: () => unarchive(c) }),
mayDelete
? h(
"button",
{
type: "button",
class: "tool arow__more",
"aria-label": t("More"),
title: t("More"),
onclick: (e) => menu(e.currentTarget, [{ label: t("Delete chat"), danger: true, run: () => remove(c) }]),
},
icon(ICONS.more),
)
: h("span", { class: "arow__more" }),
)
}
function render() {
const box = clear($("archived-body"))
const parts = []
if (error) parts.push(h("p", { class: "status is-error", text: error }))
if (loading && !rows.length) parts.push(h("p", { class: "muted page__loading", text: t("Loading…") }))
else if (!rows.length && !error) parts.push(h("p", { class: "muted arch__empty", text: t("No archived chats.") }))
if (rows.length) {
parts.push(h("ul", { class: "arch", "aria-label": t("Archived") }, rows.map(row)))
if (more) parts.push(h("button", { type: "button", class: "btn btn--sm arch__more", text: t("More"), onclick: (e) => loadMore(e.currentTarget) }))
}
box.append(h("div", { class: "arch__page" }, parts))
}
export function initArchived(context) {
ctx = context
}
+13 -1
View File
@@ -18,6 +18,7 @@ import { readyIds, sending, sent, discard } from "./attach.js"
import { watchActivity, activityChanged, planList, activityButton, treeChanged } from "./activity.js" import { watchActivity, activityChanged, planList, activityButton, treeChanged } from "./activity.js"
import { agentBlock, agentReady, agentState, onAgentChange } from "./agent.js" import { agentBlock, agentReady, agentState, onAgentChange } from "./agent.js"
import { folderName } from "./list.js" import { folderName } from "./list.js"
import { unarchive } from "./archived.js"
import { isProgress, progressLine, progressTicks } from "./progress.js" import { isProgress, progressLine, progressTicks } from "./progress.js"
import { aimTerminal, terminalButton, terminalOpen, toggleTerminal } from "./terminal.js" import { aimTerminal, terminalButton, terminalOpen, toggleTerminal } from "./terminal.js"
@@ -691,6 +692,8 @@ function renderHead(chat) {
// "Continue in a terminal" stays in the chat's menu. // "Continue in a terminal" stays in the chat's menu.
if (deviceOf(chat.device.token_id)?.terminal) tools.append(terminalButton()) if (deviceOf(chat.device.token_id)?.terminal) tools.append(terminalButton())
} }
// An archived chat says so where it is read, with the way back into the sidebar.
if (chat.archived) tools.append(h("button", { type: "button", class: "btn btn--sm head__unarchive", text: t("Restore chat"), title: t("Archived"), onclick: () => unarchiveChat(open?.chat || chat) }))
tools.append(activityButton()) tools.append(activityButton())
tools.append(h("button", { type: "button", class: "tool", "aria-label": t("More"), title: t("More"), onclick: (e) => chatMenu(e.currentTarget, open?.chat || chat) }, icon(ICONS.more))) tools.append(h("button", { type: "button", class: "tool", "aria-label": t("More"), title: t("More"), onclick: (e) => chatMenu(e.currentTarget, open?.chat || chat) }, icon(ICONS.more)))
} }
@@ -947,7 +950,7 @@ export function chatMenu(anchor, chat) {
const items = [{ label: t("Rename…"), run: () => renameChat(chat) }] const items = [{ label: t("Rename…"), run: () => renameChat(chat) }]
if (chat.kind === "agent" && chat.device_session_id) items.push({ label: t("Continue in a terminal…"), run: () => continueInTerminal(chat) }) if (chat.kind === "agent" && chat.device_session_id) items.push({ label: t("Continue in a terminal…"), run: () => continueInTerminal(chat) })
if (base) items.push({ label: t("Open in the web interface"), run: () => openLink(`${base}/chat/${encodeURIComponent(chat.id)}`) }) if (base) items.push({ label: t("Open in the web interface"), run: () => openLink(`${base}/chat/${encodeURIComponent(chat.id)}`) })
items.push(null, { label: t("Archive chat"), run: () => archiveChat(chat) }) items.push(null, chat.archived ? { label: t("Restore chat"), run: () => unarchiveChat(chat) } : { label: t("Archive chat"), run: () => archiveChat(chat) })
if (mayDelete) items.push({ label: t("Delete chat"), danger: true, run: () => deleteChat(chat) }) if (mayDelete) items.push({ label: t("Delete chat"), danger: true, run: () => deleteChat(chat) })
menu(anchor, items) menu(anchor, items)
} }
@@ -1004,6 +1007,15 @@ async function archiveChat(chat) {
} }
} }
/** An archived chat, open or in its page's menu, back into the sidebar. */
async function unarchiveChat(chat) {
if (!(await unarchive(chat))) return
if (open?.chat?.id === chat.id) {
open.chat = { ...open.chat, archived: false }
renderHead(open.chat)
}
}
async function deleteChat(chat) { async function deleteChat(chat) {
const yes = await ask({ const yes = await ask({
title: t("Delete this chat?"), title: t("Delete this chat?"),
+1
View File
@@ -99,6 +99,7 @@ export const ICONS = {
user: ["M8 2a3 3 0 110 6 3 3 0 010-6zm0 1.5a1.5 1.5 0 100 3 1.5 1.5 0 000-3z", "M2.5 14c0-2.9 2.4-4.75 5.5-4.75S13.5 11.1 13.5 14H12c0-2-1.65-3.25-4-3.25S4 12 4 14z"], user: ["M8 2a3 3 0 110 6 3 3 0 010-6zm0 1.5a1.5 1.5 0 100 3 1.5 1.5 0 000-3z", "M2.5 14c0-2.9 2.4-4.75 5.5-4.75S13.5 11.1 13.5 14H12c0-2-1.65-3.25-4-3.25S4 12 4 14z"],
check: "M6.5 10.4l6-6 1.06 1.06L6.5 12.53 2.94 8.97 4 7.9z", check: "M6.5 10.4l6-6 1.06 1.06L6.5 12.53 2.94 8.97 4 7.9z",
bell: ["M8 1.5a4.5 4.5 0 014.5 4.5v3l1.5 2.5H2L3.5 9V6A4.5 4.5 0 018 1.5zM8 3a3 3 0 00-3 3v3.42l-.35.58h6.7L11 9.42V6a3 3 0 00-3-3z", "M6.25 12.5h3.5a1.75 1.75 0 01-3.5 0z"], bell: ["M8 1.5a4.5 4.5 0 014.5 4.5v3l1.5 2.5H2L3.5 9V6A4.5 4.5 0 018 1.5zM8 3a3 3 0 00-3 3v3.42l-.35.58h6.7L11 9.42V6a3 3 0 00-3-3z", "M6.25 12.5h3.5a1.75 1.75 0 01-3.5 0z"],
archive: ["M1.5 2.5h13v3.5h-13zM3 4v.5h10V4z", "M2.5 6.5H4V12h8V6.5h1.5v7h-11z", "M6 7.5h4V9H6z"],
info: ["M8 1.5a6.5 6.5 0 110 13 6.5 6.5 0 010-13zM8 3a5 5 0 100 10A5 5 0 008 3z", "M7.25 7h1.5v4.5h-1.5zM7.25 4.5h1.5V6h-1.5z"], info: ["M8 1.5a6.5 6.5 0 110 13 6.5 6.5 0 010-13zM8 3a5 5 0 100 10A5 5 0 008 3z", "M7.25 7h1.5v4.5h-1.5zM7.25 4.5h1.5V6h-1.5z"],
} }
+20 -2
View File
@@ -19,12 +19,13 @@ import { initPalette, openPalette } from "./palette.js"
import { initActivity, showPanel, togglePanel, panelOpen } from "./activity.js" import { initActivity, showPanel, togglePanel, panelOpen } from "./activity.js"
import { initAgent, link } from "./agent.js" import { initAgent, link } from "./agent.js"
import { initScheduled, showScheduled, scheduledCommands } from "./scheduled.js" import { initScheduled, showScheduled, scheduledCommands } from "./scheduled.js"
import { initArchived, showArchived } from "./archived.js"
import { closePopover } from "./popup.js" import { closePopover } from "./popup.js"
import { noteSubagentsCap } from "./models.js" import { noteSubagentsCap } from "./models.js"
let status = null let status = null
let view = "" // "setup" | "app" let view = "" // "setup" | "app"
let page = "chat" // "chat" | "files" | "scheduled" | "settings" let page = "chat" // "chat" | "files" | "scheduled" | "archived" | "settings"
let me = null let me = null
let models = [] let models = []
let devices = [] // this account's linked devices let devices = [] // this account's linked devices
@@ -154,7 +155,7 @@ function accountButton() {
// --- The pages ---------------------------------------------------------------------------------- // --- The pages ----------------------------------------------------------------------------------
/** Show one page in the main area: the chat, the files, the scheduled tasks, or the settings. */ /** Show one page in the main area: the chat, the files, the scheduled tasks, the archived chats, or the settings. */
function showPage(name) { function showPage(name) {
page = name page = name
closePopover() closePopover()
@@ -162,6 +163,7 @@ function showPage(name) {
$("main").hidden = name !== "chat" $("main").hidden = name !== "chat"
$("page-files").hidden = name !== "files" $("page-files").hidden = name !== "files"
$("page-scheduled").hidden = name !== "scheduled" $("page-scheduled").hidden = name !== "scheduled"
$("page-archived").hidden = name !== "archived"
$("settings").hidden = name !== "settings" $("settings").hidden = name !== "settings"
for (const b of document.querySelectorAll(".navrow[data-view]")) b.setAttribute("aria-current", String(b.dataset.view === name)) for (const b of document.querySelectorAll(".navrow[data-view]")) b.setAttribute("aria-current", String(b.dataset.view === name))
if (name !== "chat") { if (name !== "chat") {
@@ -209,6 +211,11 @@ function showSettings(on, section) {
if (section) scrollToSection(section) if (section) scrollToSection(section)
} }
function showArchivedPage() {
showPage("archived")
showArchived()
}
function showFiles(which) { function showFiles(which) {
showPage("files") showPage("files")
if (which) showFilesTab(which) if (which) showFilesTab(which)
@@ -223,6 +230,7 @@ function commands() {
{ label: t("Scheduled"), run: () => (showPage("scheduled"), showScheduled()) }, { label: t("Scheduled"), run: () => (showPage("scheduled"), showScheduled()) },
...scheduledCommands(), ...scheduledCommands(),
{ label: t("Files"), run: () => showFiles() }, { label: t("Files"), run: () => showFiles() },
{ label: t("Archived"), run: showArchivedPage },
{ label: t("Settings"), run: () => showSettings(true) }, { label: t("Settings"), run: () => showSettings(true) },
] ]
if (openChatId()) out.push({ label: panelOpen() ? t("Close the activity panel") : t("Open the activity panel"), run: togglePanel }) if (openChatId()) out.push({ label: panelOpen() ? t("Close the activity panel") : t("Open the activity panel"), run: togglePanel })
@@ -291,6 +299,12 @@ async function boot() {
}, },
}) })
initAttach({ chat: () => openChatId() || "", allowed: () => me?.permissions?.["files.upload"] !== false }) initAttach({ chat: () => openChatId() || "", allowed: () => me?.permissions?.["files.upload"] !== false })
initArchived({
me: () => me,
open: (id) => showChat(id),
// Back in the sidebar: the list again.
changed: () => refreshList(),
})
initPalette({ commands, open: (c) => showChat(c.id) }) initPalette({ commands, open: (c) => showChat(c.id) })
initScheduled({ status: () => status, models: () => models, me: () => me, openChat: (id) => showChat(id), show: () => showPage("scheduled") }) initScheduled({ status: () => status, models: () => models, me: () => me, openChat: (id) => showChat(id), show: () => showPage("scheduled") })
onViewer((on) => { onViewer((on) => {
@@ -326,6 +340,10 @@ async function boot() {
}) })
$("nav-files").querySelector(".navrow__icon").replaceChildren(icon(ICONS.folder)) $("nav-files").querySelector(".navrow__icon").replaceChildren(icon(ICONS.folder))
$("nav-files").addEventListener("click", () => showFiles()) $("nav-files").addEventListener("click", () => showFiles())
$("nav-archived").querySelector(".navrow__icon").replaceChildren(icon(ICONS.archive))
$("nav-archived").addEventListener("click", showArchivedPage)
$("archived-refresh").replaceChildren(icon(ICONS.refresh))
$("archived-refresh").addEventListener("click", () => showArchived())
accountButton() accountButton()
$("settings-btn").addEventListener("click", () => showSettings(page !== "settings")) $("settings-btn").addEventListener("click", () => showSettings(page !== "settings"))
$("settings-close").replaceChildren(icon(ICONS.close)) $("settings-close").replaceChildren(icon(ICONS.close))