Files
project-lyra/lyra/web/static/nav.js
T
serversdown 5176c706b6 feat: thought loop — Lyra's threaded, surfaceable train of thought
Built from her own 6-19 idea: a continuing train of thought she keeps across
days, organized into threads she returns to, that she can bring TO Brian and
that his feedback advances or closes. Where the dream cycle's reflect() gives
isolated, overwriting reflections, the thought loop adds continuity (threads),
surfacing (#6 — she leads with a thought when Brian returns after a gap), and a
feedback loop (his reply folds in next pass).

- lyra/thoughts.py: thought_threads + thoughts tables; think() with
  new/continue/respond modes; salience-gated maybe_surface(); record_response()
  feedback; lazy-schema _c() mirroring poker.
- dream.py: curiosity stage advances the loop after reflecting (error-isolated).
- chat.py: build_messages surfaces the top thread after a >=90min gap, once.
- web: /thoughts feed (page + data + respond + status routes), thoughts.html,
  nav 💭 entry. lyra-think entry point. Every thought also lands in her journal.
- clock.gap_seconds(); tests/test_thoughts.py (8 tests). Full suite 58 passing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 07:05:15 +00:00

78 lines
3.7 KiB
JavaScript

/* Shared app navigation — one source of truth across all pages (no build step).
Injects a left sidebar on desktop (>=769px) with active-page highlighting; stays
out of the way on mobile, where each page keeps its bottom bar / back-links. */
(function () {
const ITEMS = [
{ href: "/", icon: "💬", label: "Chat" },
{ href: "/session", icon: "♠", label: "Session" },
{ href: "/history", icon: "📚", label: "History" },
{ href: "/hands", icon: "🃏", label: "Hands" },
{ href: "/self", icon: "🧠", label: "Mind" },
{ href: "/thoughts", icon: "💭", label: "Thoughts" },
{ href: "/journal", icon: "📔", label: "Journal" },
{ href: "/logs", icon: "📜", label: "Logs" },
];
const path = location.pathname;
function isActive(href) {
if (href === "/") return path === "/" || path === "";
if (href === "/hands") return path === "/hands" || path.indexOf("/hand") === 0;
if (href === "/history") return path.indexOf("/history") === 0 || path.indexOf("/recap") === 0;
return path === href || path.indexOf(href + "/") === 0;
}
const css = `
#app-nav { display: none; }
@media screen and (min-width: 769px) {
body { padding-left: 212px; }
#app-nav {
position: fixed; left: 0; top: 0; bottom: 0; width: 212px; z-index: 1000;
display: flex; flex-direction: column; gap: 2px; box-sizing: border-box;
padding: 14px 10px; background: #0b0b0b; border-right: 1px solid #2a1d12;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
#app-nav .brand {
display: flex; align-items: center; gap: 8px; text-decoration: none;
color: #ff7a00; font-weight: 700; font-size: 1.15rem; letter-spacing: .5px;
padding: 6px 11px 14px;
}
#app-nav .brand .dot { width: 8px; height: 8px; border-radius: 50%;
background: #8fd694; box-shadow: 0 0 8px rgba(143,214,148,.6); }
#app-nav .navitem {
display: flex; align-items: center; gap: 11px; width: 100%; text-align: left;
padding: 9px 11px; border-radius: 9px; border: none; background: none;
color: #cfcfcf; text-decoration: none; font-size: .95rem; cursor: pointer;
font-family: inherit; -webkit-tap-highlight-color: transparent;
}
#app-nav .navitem .i { font-size: 1.05rem; width: 20px; text-align: center; filter: grayscale(.3); }
#app-nav .navitem:hover { background: rgba(255,122,0,.08); color: #fff; }
#app-nav .navitem.active { background: rgba(255,122,0,.14); color: #ff7a00; }
#app-nav .navitem.active .i { filter: none; }
#app-nav .spacer { flex: 1; }
}`;
const style = document.createElement("style");
style.textContent = css;
document.head.appendChild(style);
const nav = document.createElement("nav");
nav.id = "app-nav";
nav.setAttribute("aria-label", "App navigation");
nav.innerHTML =
'<a class="brand" href="/"><span class="dot"></span> Lyra</a>' +
ITEMS.map(function (it) {
return '<a class="navitem' + (isActive(it.href) ? " active" : "") + '" href="' + it.href + '">' +
'<span class="i">' + it.icon + '</span><span class="l">' + it.label + "</span></a>";
}).join("") +
'<div class="spacer"></div>' +
'<button class="navitem" id="navSettings" type="button"><span class="i">⚙</span><span class="l">Settings</span></button>';
document.body.insertBefore(nav, document.body.firstChild);
// Settings opens the chat-page modal; from other pages, jump to chat and open it.
nav.querySelector("#navSettings").addEventListener("click", function () {
const btn = document.getElementById("settingsBtn");
if (btn) btn.click();
else location.href = "/?settings=1";
});
})();