Revamp workspace archive handling with async support, input validation improvements and better session management
CI / check (22) (push) Failing after 4m49s
CI / check (24) (push) Failing after 4m55s

This commit is contained in:
2026-10-13 18:19:20 +02:00
parent 068843beda
commit d5d886ac5f
9 changed files with 1266 additions and 1113 deletions
+13 -13
View File
@@ -17,8 +17,8 @@
</head> </head>
<body class="site-page account-page"> <body class="site-page account-page">
<!-- site-header --> <!-- site-header -->
<main id="main" class="account-layout page-content"> <main id="main" class="account-layout page-content">
<section class="account-intro"> <section class="account-intro">
<p class="eyebrow">Welcome to elsewhere</p> <p class="eyebrow">Welcome to elsewhere</p>
<h1 id="accountHeadline">Pick up where<br>curiosity left off.</h1> <h1 id="accountHeadline">Pick up where<br>curiosity left off.</h1>
@@ -43,9 +43,9 @@
aria-pressed="false"> aria-pressed="false">
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" <svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor"
stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12Z" /> <path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12Z"/>
<circle cx="12" cy="12" r="3" /> <circle cx="12" cy="12" r="3"/>
<path class="eye-slash" d="m3 3 18 18" /> <path class="eye-slash" d="m3 3 18 18"/>
</svg> </svg>
</button> </button>
</div> </div>
@@ -63,8 +63,8 @@
aria-atomic="true" tabindex="-1"> aria-atomic="true" tabindex="-1">
<svg class="account-error-icon" viewBox="0 0 24 24" width="20" height="20" fill="none" <svg class="account-error-icon" viewBox="0 0 24 24" width="20" height="20" fill="none"
stroke="currentColor" stroke-width="2" aria-hidden="true"> stroke="currentColor" stroke-width="2" aria-hidden="true">
<circle cx="12" cy="12" r="9" /> <circle cx="12" cy="12" r="9"/>
<path d="M12 7v6m0 3v1" /> <path d="M12 7v6m0 3v1"/>
</svg> </svg>
<span id="accountStatusText"></span> <span id="accountStatusText"></span>
</div> </div>
@@ -73,16 +73,16 @@
<p id="accountSwitch"> <p id="accountSwitch">
</p> </p>
</section> </section>
</main> </main>
<footer class="site-footer"> <footer class="site-footer">
<a class="site-brand" href="/">elsewhere</a> <a class="site-brand" href="/">elsewhere</a>
<span>A space for every conversation.</span> <span>A space for every conversation.</span>
<a href="/privacy.html">Privacy</a> <a href="/privacy.html">Privacy</a>
<a href="/tos.html">Terms</a> <a href="/tos.html">Terms</a>
</footer> </footer>
<script type="module" src="/js/pages.js?v=20260909-account-controls"> <script type="module" src="/js/pages.js?v=20260909-account-controls">
</script> </script>
<script type="module" src="/js/workspaceNavigation.js?v=20260909-account-controls"></script> <script type="module" src="/js/workspaceNavigation.js?v=20260909-account-controls"></script>
</body> </body>
</html> </html>
+117 -69
View File
@@ -2,7 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8"/>
<link rel="icon" type="image/svg+xml" href="/img/favicon.svg"> <link rel="icon" type="image/svg+xml" href="/img/favicon.svg">
<title>Elsewhere</title> <title>Elsewhere</title>
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
@@ -11,26 +11,28 @@
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap" href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap"
rel="stylesheet"> rel="stylesheet">
<script src="/js/theme-bootstrap.js?v=20260909-account-controls"></script> <script src="/js/theme-bootstrap.js?v=20260909-account-controls"></script>
<link rel="stylesheet" href="/css/style.css?v=20261012-chat-usability" /> <link rel="stylesheet" href="/css/style.css?v=20261012-chat-usability"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/css/pages.css?v=20260909-account-controls"> <link rel="stylesheet" href="/css/pages.css?v=20260909-account-controls">
<link rel="stylesheet" href="/css/shell.css?v=20260909-account-controls"> <link rel="stylesheet" href="/css/shell.css?v=20260909-account-controls">
</head> </head>
<body class="site-page chat-page" data-theme="fakegpt" data-theme-mode="light"> <body class="site-page chat-page" data-theme="fakegpt" data-theme-mode="light">
<!-- site-header --> <!-- site-header -->
<div class="app-shell"> <div class="app-shell">
<section id="chat" class="card chat-card chat-focus-card hidden" aria-label="AI chat workspace"> <section id="chat" class="card chat-card chat-focus-card hidden" aria-label="AI chat workspace">
<div class="chat-layout chat-layout-focus"> <div class="chat-layout chat-layout-focus">
<aside id="chatSidebar" class="chat-sidebar" aria-label="Conversation history"> <aside id="chatSidebar" class="chat-sidebar" aria-label="Conversation history">
<div class="sidebar-top"> <div class="sidebar-top">
<div class="sidebar-workspace-card"> <div class="sidebar-workspace-card">
<h2>Conversations</h2><button id="newChat" class="primary-action" type="button">New <h2>Conversations</h2>
chat</button> <button id="newChat" class="primary-action" type="button">New
chat
</button>
</div> </div>
<label class="field compact-field"> <label class="field compact-field">
<span>Search</span> <span>Search</span>
<input id="chatSearch" type="search" placeholder="Search chats" /> <input id="chatSearch" type="search" placeholder="Search chats"/>
</label> </label>
</div> </div>
<div class="sidebar-middle"> <div class="sidebar-middle">
@@ -50,7 +52,8 @@
<header class="chat-header chat-header-focus"> <header class="chat-header chat-header-focus">
<button id="chatSidebarToggle" class="icon-button sidebar-toggle" type="button" <button id="chatSidebarToggle" class="icon-button sidebar-toggle" type="button"
aria-controls="chatSidebar" aria-expanded="false" aria-controls="chatSidebar" aria-expanded="false"
aria-label="Open conversation history">History</button> aria-label="Open conversation history">History
</button>
<div class="chat-title-block"> <div class="chat-title-block">
<div class="chat-title"> <div class="chat-title">
<h2 id="activeChatTitle">New chat</h2> <h2 id="activeChatTitle">New chat</h2>
@@ -61,7 +64,8 @@
aria-controls="chatDrawer" aria-controls="chatDrawer"
aria-label="Change AI model"><span>Model</span><strong aria-label="Change AI model"><span>Model</span><strong
id="headerModelName">Loading...</strong></button> id="headerModelName">Loading...</strong></button>
<label class="chat-context-item"><span>Chat with</span><select id="personalitySelect" aria-label="Conversation personality"></select></label> <label class="chat-context-item"><span>Chat with</span><select id="personalitySelect"
aria-label="Conversation personality"></select></label>
</div> </div>
</div> </div>
<div class="chat-header-actions"> <div class="chat-header-actions">
@@ -79,7 +83,8 @@
<h2 id="chatDrawerTitle">Settings</h2> <h2 id="chatDrawerTitle">Settings</h2>
</div> </div>
<button id="chatDrawerClose" class="icon-button" type="button" <button id="chatDrawerClose" class="icon-button" type="button"
aria-label="Close chat settings">×</button> aria-label="Close chat settings">×
</button>
</div> </div>
<div class="chat-drawer-body"> <div class="chat-drawer-body">
<div class="chat-drawer-summary"> <div class="chat-drawer-summary">
@@ -131,23 +136,26 @@
</div> </div>
</details> </details>
<details class="chat-drawer-section generation-advanced"> <details class="chat-drawer-section generation-advanced">
<summary class="chat-drawer-row"><span class="chat-drawer-row-label"><strong>Generation settings</strong></span><span class="chat-drawer-row-hint">Advanced</span></summary> <summary class="chat-drawer-row"><span
class="chat-drawer-row-label"><strong>Generation settings</strong></span><span
class="chat-drawer-row-hint">Advanced</span></summary>
<div class="generation-settings-grid chat-drawer-disclosure-panel"> <div class="generation-settings-grid chat-drawer-disclosure-panel">
<label class="field"><span>Temperature</span><input <label class="field"><span>Temperature</span><input
id="chatTemperature" type="number" min="0" max="2" id="chatTemperature" type="number" min="0" max="2"
step="0.1" placeholder="Default" /></label> step="0.1" placeholder="Default"/></label>
<label class="field"><span>Context length</span><input <label class="field"><span>Context length</span><input
id="chatContextLength" type="number" min="256" id="chatContextLength" type="number" min="256"
max="131072" placeholder="Default" /></label> max="131072" placeholder="Default"/></label>
<label class="field"><span>Response length</span><input <label class="field"><span>Response length</span><input
id="chatResponseLength" type="number" min="1" id="chatResponseLength" type="number" min="1"
max="32768" placeholder="Default" /></label> max="32768" placeholder="Default"/></label>
<label class="field field-wide"><span>System <label class="field field-wide"><span>System
instruction</span><textarea instruction</span><textarea
id="chatSystemInstruction" rows="4" maxlength="8000" id="chatSystemInstruction" rows="4" maxlength="8000"
placeholder="Optional instructions for this chat"></textarea></label> placeholder="Optional instructions for this chat"></textarea></label>
<button id="saveGenerationSettings" type="button">Save <button id="saveGenerationSettings" type="button">Save
generation settings</button> generation settings
</button>
</div> </div>
</details> </details>
<div class="chat-drawer-section"> <div class="chat-drawer-section">
@@ -155,15 +163,19 @@
<div class="chat-drawer-action-list"> <div class="chat-drawer-action-list">
<button id="renameChat" <button id="renameChat"
class="secondary-action chat-drawer-action" type="button" class="secondary-action chat-drawer-action" type="button"
data-chat-action="rename">Rename chat</button> data-chat-action="rename">Rename chat
</button>
<button id="pinChat" class="secondary-action chat-drawer-action" <button id="pinChat" class="secondary-action chat-drawer-action"
type="button" data-chat-action="pin">Pin</button> type="button" data-chat-action="pin">Pin
</button>
<button id="archiveChat" <button id="archiveChat"
class="secondary-action chat-drawer-action" type="button" class="secondary-action chat-drawer-action" type="button"
data-chat-action="archive">Archive</button> data-chat-action="archive">Archive
</button>
<button id="exportChat" <button id="exportChat"
class="secondary-action chat-drawer-action" type="button" class="secondary-action chat-drawer-action" type="button"
data-chat-action="export">Export</button> data-chat-action="export">Export
</button>
</div> </div>
</div> </div>
<details class="chat-drawer-section chat-drawer-more"> <details class="chat-drawer-section chat-drawer-more">
@@ -176,27 +188,37 @@
<div class="chat-drawer-action-list chat-drawer-disclosure-panel"> <div class="chat-drawer-action-list chat-drawer-disclosure-panel">
<button id="moveChatFolder" <button id="moveChatFolder"
class="secondary-action chat-drawer-action" type="button" class="secondary-action chat-drawer-action" type="button"
data-chat-action="folder">Move to folder</button> data-chat-action="folder">Move to folder
</button>
<button id="manageChatMemory" <button id="manageChatMemory"
class="secondary-action chat-drawer-action" type="button" class="secondary-action chat-drawer-action" type="button"
data-chat-action="memory">Conversation memory</button> data-chat-action="memory">Conversation memory
<button id="exploreBranches" type="button" class="secondary-action chat-drawer-action" data-chat-action="branches">Branches &amp; compare</button> </button>
<button id="exploreBranches" type="button"
class="secondary-action chat-drawer-action"
data-chat-action="branches">Branches &amp; compare
</button>
<button id="managePersonaState" <button id="managePersonaState"
class="secondary-action chat-drawer-action" type="button" class="secondary-action chat-drawer-action" type="button"
data-chat-action="personaState">Roleplay state</button> data-chat-action="personaState">Roleplay state
</button>
</div> </div>
</details> </details>
<details class="chat-drawer-section chat-drawer-more"> <details class="chat-drawer-section chat-drawer-more">
<summary class="chat-drawer-row chat-drawer-row--compact"><span class="chat-drawer-row-label"><strong>Workspace files</strong></span><span class="chat-drawer-row-hint">Show</span></summary> <summary class="chat-drawer-row chat-drawer-row--compact"><span
class="chat-drawer-row-label"><strong>Workspace files</strong></span><span
class="chat-drawer-row-hint">Show</span></summary>
<div class="chat-drawer-action-list chat-drawer-disclosure-panel"> <div class="chat-drawer-action-list chat-drawer-disclosure-panel">
<button id="backupWorkspace" <button id="backupWorkspace"
class="secondary-action chat-drawer-action" class="secondary-action chat-drawer-action"
type="button">Download workspace archive</button> type="button">Download workspace archive
</button>
<button id="importWorkspace" <button id="importWorkspace"
class="secondary-action chat-drawer-action" class="secondary-action chat-drawer-action"
type="button">Import workspace</button> type="button">Import workspace
</button>
<input id="importWorkspaceFile" class="hidden" type="file" <input id="importWorkspaceFile" class="hidden" type="file"
accept="application/json,.json,.tar.gz,application/gzip" /> accept="application/json,.json,.tar.gz,application/gzip"/>
</div> </div>
</details> </details>
<details class="chat-drawer-section chat-drawer-danger"> <details class="chat-drawer-section chat-drawer-danger">
@@ -209,7 +231,8 @@
<div class="chat-drawer-action-list chat-drawer-disclosure-panel"> <div class="chat-drawer-action-list chat-drawer-disclosure-panel">
<button id="clearChat" <button id="clearChat"
class="secondary-action chat-drawer-action chat-drawer-action-danger" class="secondary-action chat-drawer-action chat-drawer-action-danger"
type="button" data-chat-action="clear">Clear chat</button> type="button" data-chat-action="clear">Clear chat
</button>
</div> </div>
</details> </details>
</div> </div>
@@ -236,20 +259,35 @@
<div class="messages-wrap"> <div class="messages-wrap">
<div class="history-controls"> <div class="history-controls">
<button id="loadOlderMessages" class="secondary-action" type="button" hidden>Load older messages</button> <button id="loadOlderMessages" class="secondary-action" type="button" hidden>Load older
<button id="loadLatestMessages" class="secondary-action" type="button" hidden>Back to latest messages</button> messages
</button>
<button id="loadLatestMessages" class="secondary-action" type="button" hidden>Back to latest
messages
</button>
</div> </div>
<button id="conversationSearchToggle" class="conversation-search-toggle secondary-action" type="button" aria-controls="conversationSearchBar" aria-expanded="false">Find in this chat</button> <button id="conversationSearchToggle" class="conversation-search-toggle secondary-action"
<div id="conversationSearchBar" class="conversation-search" role="search" aria-label="Search this conversation"> type="button" aria-controls="conversationSearchBar" aria-expanded="false">Find in this chat
</button>
<div id="conversationSearchBar" class="conversation-search" role="search"
aria-label="Search this conversation">
<label for="conversationSearch">Find in this chat</label> <label for="conversationSearch">Find in this chat</label>
<input id="conversationSearch" type="search" placeholder="Search messages" autocomplete="off" maxlength="200"> <input id="conversationSearch" type="search" placeholder="Search messages" autocomplete="off"
maxlength="200">
<span id="conversationSearchCount" role="status" aria-live="polite">0 matches</span> <span id="conversationSearchCount" role="status" aria-live="polite">0 matches</span>
<button id="conversationSearchPrevious" class="secondary-action" type="button" aria-label="Previous match" disabled>Previous</button> <button id="conversationSearchPrevious" class="secondary-action" type="button"
<button id="conversationSearchNext" class="secondary-action" type="button" aria-label="Next match" disabled>Next</button> aria-label="Previous match" disabled>Previous
</button>
<button id="conversationSearchNext" class="secondary-action" type="button"
aria-label="Next match" disabled>Next
</button>
</div> </div>
<div id="messages" class="messages-panel messages-panel-focus" role="log" aria-live="off"></div> <div id="messages" class="messages-panel messages-panel-focus" role="log" aria-live="off"></div>
<div id="replyAnnouncement" class="visually-hidden" role="status" aria-live="polite" aria-atomic="true"></div> <div id="replyAnnouncement" class="visually-hidden" role="status" aria-live="polite"
<button id="jumpToLatest" class="jump-to-latest secondary-action hidden" type="button">Jump to latest</button> aria-atomic="true"></div>
<button id="jumpToLatest" class="jump-to-latest secondary-action hidden" type="button">Jump to
latest
</button>
</div> </div>
<div class="input-shell input-shell-focus"> <div class="input-shell input-shell-focus">
@@ -264,19 +302,23 @@
title="Attach image"> title="Attach image">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1.6" aria-hidden="true"> stroke-width="1.6" aria-hidden="true">
<rect x="3" y="3" width="18" height="18" rx="3" /> <rect x="3" y="3" width="18" height="18" rx="3"/>
<circle cx="8" cy="8" r="1.5" /> <circle cx="8" cy="8" r="1.5"/>
<path d="m3 17 5-5 4 4 4-6 5 7" /> <path d="m3 17 5-5 4 4 4-6 5 7"/>
</svg><span class="composer-tool-label">Attach</span> </svg>
<span class="composer-tool-label">Attach</span>
</button> </button>
<button id="webSearch" class="icon-button" type="button" aria-label="Web search allowed. The assistant searches when useful. Click to turn off." <button id="webSearch" class="icon-button" type="button"
title="Web search allowed. The assistant searches when useful. Click to turn off." aria-pressed="true"> aria-label="Web search allowed. The assistant searches when useful. Click to turn off."
title="Web search allowed. The assistant searches when useful. Click to turn off."
aria-pressed="true">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1.6" aria-hidden="true"> stroke-width="1.6" aria-hidden="true">
<circle cx="12" cy="12" r="9" /> <circle cx="12" cy="12" r="9"/>
<ellipse cx="12" cy="12" rx="4" ry="9" /> <ellipse cx="12" cy="12" rx="4" ry="9"/>
<path d="M3 12h18" /> <path d="M3 12h18"/>
</svg><span class="composer-tool-label">Web search</span> </svg>
<span class="composer-tool-label">Web search</span>
</button> </button>
<button id="reasoning" class="icon-button" type="button" aria-label="Enable reasoning" <button id="reasoning" class="icon-button" type="button" aria-label="Enable reasoning"
title="Enable reasoning" aria-pressed="false" disabled hidden> title="Enable reasoning" aria-pressed="false" disabled hidden>
@@ -284,22 +326,26 @@
stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"
aria-hidden="true"> aria-hidden="true">
<path <path
d="M12 18V5a3 3 0 0 0-5.8-1A4 4 0 0 0 3 10a4 4 0 0 0 1 7.8A4 4 0 0 0 12 18ZM12 5a3 3 0 0 1 5.8-1A4 4 0 0 1 21 10a4 4 0 0 1-1 7.8A4 4 0 0 1 12 18M8 8a4 4 0 0 1-5 2m13-2a4 4 0 0 0 5 2M8 14a4 4 0 0 0-4 4m12-4a4 4 0 0 1 4 4" /> d="M12 18V5a3 3 0 0 0-5.8-1A4 4 0 0 0 3 10a4 4 0 0 0 1 7.8A4 4 0 0 0 12 18ZM12 5a3 3 0 0 1 5.8-1A4 4 0 0 1 21 10a4 4 0 0 1-1 7.8A4 4 0 0 1 12 18M8 8a4 4 0 0 1-5 2m13-2a4 4 0 0 0 5 2M8 14a4 4 0 0 0-4 4m12-4a4 4 0 0 1 4 4"/>
</svg><span class="composer-tool-label">Reasoning</span> </svg>
<span class="composer-tool-label">Reasoning</span>
</button> </button>
<label class="visually-hidden" for="msgInput">Message</label> <label class="visually-hidden" for="msgInput">Message</label>
<textarea id="msgInput" rows="1" placeholder="Type your message..."></textarea> <textarea id="msgInput" rows="1" placeholder="Type your message..."></textarea>
<button id="send" type="button" aria-label="Send message"></button> <button id="send" type="button" aria-label="Send message"></button>
<button id="stopGeneration" class="secondary-action" type="button" aria-label="Stop generating reply" hidden>Stop</button> <button id="stopGeneration" class="secondary-action" type="button"
aria-label="Stop generating reply" hidden>Stop
</button>
</div> </div>
</div> </div>
</section> </section>
</div> </div>
</section> </section>
</div> </div>
<div id="personaModal" class="modal-overlay side-panel-overlay hidden"> <div id="personaModal" class="modal-overlay side-panel-overlay hidden">
<div class="modal-card persona-modal-card side-panel-card" role="dialog" aria-modal="true" aria-labelledby="personaFormTitle" tabindex="-1"> <div class="modal-card persona-modal-card side-panel-card" role="dialog" aria-modal="true"
aria-labelledby="personaFormTitle" tabindex="-1">
<div class="modal-header"> <div class="modal-header">
<div class="persona-modal-heading"> <div class="persona-modal-heading">
<p class="eyebrow">Persona editor</p> <p class="eyebrow">Persona editor</p>
@@ -323,11 +369,11 @@
</label> </label>
<label class="field"> <label class="field">
<span>Persona name</span> <span>Persona name</span>
<input id="personaName" placeholder="Persona name" required /> <input id="personaName" placeholder="Persona name" required/>
</label> </label>
<label class="field"> <label class="field">
<span>Persona pronouns</span> <span>Persona pronouns</span>
<input id="personaPronouns" placeholder="e.g. she/her, they/them" /> <input id="personaPronouns" placeholder="e.g. she/her, they/them"/>
</label> </label>
<label class="field field-wide"> <label class="field field-wide">
<span>Appearance</span> <span>Appearance</span>
@@ -360,10 +406,11 @@
</div> </div>
</form> </form>
</div> </div>
</div> </div>
<div id="roleplayStarterModal" class="modal-overlay side-panel-overlay hidden"> <div id="roleplayStarterModal" class="modal-overlay side-panel-overlay hidden">
<div class="modal-card roleplay-starter-card side-panel-card" role="dialog" aria-modal="true" aria-labelledby="roleplayStarterTitle" tabindex="-1"> <div class="modal-card roleplay-starter-card side-panel-card" role="dialog" aria-modal="true"
aria-labelledby="roleplayStarterTitle" tabindex="-1">
<div class="modal-header"> <div class="modal-header">
<div class="persona-modal-heading"> <div class="persona-modal-heading">
<p class="eyebrow">Start roleplay</p> <p class="eyebrow">Start roleplay</p>
@@ -405,9 +452,9 @@
<button id="roleplayStarterConfirm" type="button">Continue</button> <button id="roleplayStarterConfirm" type="button">Continue</button>
</div> </div>
</div> </div>
</div> </div>
<div id="popupModal" class="modal-overlay hidden"> <div id="popupModal" class="modal-overlay hidden">
<div class="modal-card popup-card" role="dialog" aria-modal="true" aria-labelledby="popupTitle" <div class="modal-card popup-card" role="dialog" aria-modal="true" aria-labelledby="popupTitle"
aria-describedby="popupDescription"> aria-describedby="popupDescription">
<div class="modal-header"> <div class="modal-header">
@@ -421,7 +468,7 @@
<p id="popupDescription" class="subtitle">Please confirm this action.</p> <p id="popupDescription" class="subtitle">Please confirm this action.</p>
<label id="popupField" class="field hidden"> <label id="popupField" class="field hidden">
<span id="popupInputLabel">Value</span> <span id="popupInputLabel">Value</span>
<input id="popupInput" type="text" autocomplete="off" /> <input id="popupInput" type="text" autocomplete="off"/>
</label> </label>
</div> </div>
<div class="popup-actions"> <div class="popup-actions">
@@ -429,10 +476,11 @@
<button id="popupConfirm" type="button">Confirm</button> <button id="popupConfirm" type="button">Confirm</button>
</div> </div>
</div> </div>
</div> </div>
<div id="onboardingModal" class="modal-overlay hidden"> <div id="onboardingModal" class="modal-overlay hidden">
<div class="modal-card onboarding-card" role="dialog" aria-modal="true" aria-labelledby="onboardingTitle" aria-describedby="onboardingSubtitle" tabindex="-1"> <div class="modal-card onboarding-card" role="dialog" aria-modal="true" aria-labelledby="onboardingTitle"
aria-describedby="onboardingSubtitle" tabindex="-1">
<div class="modal-header"> <div class="modal-header">
<div class="persona-modal-heading"> <div class="persona-modal-heading">
<p class="eyebrow">Get started</p> <p class="eyebrow">Get started</p>
@@ -447,12 +495,12 @@
<button id="onboardingContinue" type="button">Continue</button> <button id="onboardingContinue" type="button">Continue</button>
</div> </div>
</div> </div>
</div> </div>
<script src="/vendor/marked.min.js" defer></script> <script src="/vendor/marked.min.js" defer></script>
<script src="/vendor/dompurify.js" defer></script> <script src="/vendor/dompurify.js" defer></script>
<script type="module" src="/js/script.js?v=20261012-chat-usability"></script> <script type="module" src="/js/script.js?v=20261012-chat-usability"></script>
<script type="module" src="/js/workspaceNavigation.js?v=20260909-account-controls"></script> <script type="module" src="/js/workspaceNavigation.js?v=20260909-account-controls"></script>
</body> </body>
</html> </html>
+6 -6
View File
@@ -17,9 +17,9 @@
</head> </head>
<body class="site-page home-page"> <body class="site-page home-page">
<!-- site-header --> <!-- site-header -->
<main id="main" class="home-main page-content"> <main id="main" class="home-main page-content">
<p id="welcomeName" class="eyebrow">Welcome home</p> <p id="welcomeName" class="eyebrow">Welcome home</p>
<h1>What would you like to do?</h1> <h1>What would you like to do?</h1>
<p class="hero-copy">Start somewhere new, or pick up a conversation.</p> <p class="hero-copy">Start somewhere new, or pick up a conversation.</p>
@@ -52,11 +52,11 @@
</section> </section>
<p id="homeStatus" role="status"> <p id="homeStatus" role="status">
</p> </p>
</main> </main>
<script type="module" src="/js/pages.js?v=20260909-account-controls"> <script type="module" src="/js/pages.js?v=20260909-account-controls">
</script> </script>
<script type="module" src="/js/workspaceNavigation.js?v=20260909-account-controls"></script> <script type="module" src="/js/workspaceNavigation.js?v=20260909-account-controls"></script>
</body> </body>
</html> </html>
+6 -6
View File
@@ -16,8 +16,8 @@
</head> </head>
<body class="site-page landing-page"> <body class="site-page landing-page">
<!-- site-header --> <!-- site-header -->
<main id="main" class="landing-entry"> <main id="main" class="landing-entry">
<div class="entry-door" aria-hidden="true"><span></span></div> <div class="entry-door" aria-hidden="true"><span></span></div>
<div class="entry-copy"> <div class="entry-copy">
<p class="eyebrow">A space for every conversation.</p> <p class="eyebrow">A space for every conversation.</p>
@@ -28,11 +28,11 @@
aria-hidden="true">&rarr;</span></a> aria-hidden="true">&rarr;</span></a>
</div> </div>
</div> </div>
</main> </main>
<footer class="site-footer"><span>elsewhere / a little room for imagination</span><a <footer class="site-footer"><span>elsewhere / a little room for imagination</span><a
href="/privacy.html">Privacy</a><a href="/tos.html">Terms</a></footer> href="/privacy.html">Privacy</a><a href="/tos.html">Terms</a></footer>
<script type="module" src="/js/pages.js?v=20260909-account-controls"></script> <script type="module" src="/js/pages.js?v=20260909-account-controls"></script>
<script type="module" src="/js/workspaceNavigation.js?v=20260909-account-controls"></script> <script type="module" src="/js/workspaceNavigation.js?v=20260909-account-controls"></script>
</body> </body>
</html> </html>
+25 -2
View File
@@ -1,3 +1,26 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><link rel="icon" href="/img/favicon.svg"><title>Personalities - Elsewhere</title><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap" rel="stylesheet"><script src="/js/theme-bootstrap.js"></script><link rel="stylesheet" href="/css/style.css"><link rel="stylesheet" href="/css/pages.css"><link rel="stylesheet" href="/css/shell.css"></head> <html lang="en">
<body class="site-page personalities-page" data-theme="fakegpt" data-theme-mode="light"><!-- site-header --><main id="main" class="personality-shell"><header class="personality-page-header"><p class="eyebrow">Choose your approach</p><h1>Personalities</h1><p class="subtitle">Six specialized assistants, each designed for a different kind of work.</p></header><section id="personalityGrid" class="personality-grid" aria-live="polite"></section></main><script type="module" src="/js/personalities.js"></script></body></html> <head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="/img/favicon.svg">
<title>Personalities - Elsewhere</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap"
rel="stylesheet">
<script src="/js/theme-bootstrap.js"></script>
<link rel="stylesheet" href="/css/style.css">
<link rel="stylesheet" href="/css/pages.css">
<link rel="stylesheet" href="/css/shell.css">
</head>
<body class="site-page personalities-page" data-theme="fakegpt" data-theme-mode="light"><!-- site-header -->
<main id="main" class="personality-shell">
<header class="personality-page-header"><p class="eyebrow">Choose your approach</p>
<h1>Personalities</h1>
<p class="subtitle">Six specialized assistants, use whichever fits you best.</p></header>
<section id="personalityGrid" class="personality-grid" aria-live="polite"></section>
</main>
<script type="module" src="/js/personalities.js"></script>
</body>
</html>
+96 -33
View File
@@ -17,20 +17,24 @@
</head> </head>
<body class="site-page legal-page"> <body class="site-page legal-page">
<!-- site-header --> <!-- site-header -->
<main id="main" class="legal-shell"> <main id="main" class="legal-shell">
<header class="legal-hero"> <header class="legal-hero">
<p class="eyebrow">Your data, explained</p> <p class="eyebrow">Your data, explained</p>
<h1>Privacy Policy</h1> <h1>Privacy Policy</h1>
<p class="legal-summary">This policy explains what personal data Elsewhere handles, why it is needed, where <p class="legal-summary">This policy explains what personal data Elsewhere handles, why it is needed, where
it goes and the choices available to you.</p> it goes and the choices available to you.</p>
<p class="legal-updated">Effective and last updated <time datetime="2026-09-23">23 September 2026</time></p> <p class="legal-updated">Effective and last updated
<time datetime="2026-09-23">23 September 2026</time>
</p>
</header> </header>
<aside class="legal-callout" aria-label="Privacy summary"> <aside class="legal-callout" aria-label="Privacy summary">
<div><strong>No advertising</strong><span>We do not sell personal data or use it for targeted ads.</span></div> <div><strong>No advertising</strong><span>We do not sell personal data or use it for targeted ads.</span></div>
<div><strong>You stay in control</strong><span>Export your workspace or delete your account in Settings.</span></div> <div><strong>You stay in control</strong><span>Export your workspace or delete your account in Settings.</span>
<div><strong>Features are optional</strong><span>Web search, image uploads and public publishing are choices you make.</span></div> </div>
<div><strong>Features are optional</strong><span>Web search, image uploads and public publishing are choices you make.</span>
</div>
</aside> </aside>
<div class="legal-layout"> <div class="legal-layout">
@@ -58,9 +62,19 @@
<p>Kristian Cerni is the controller responsible for Elsewhere. This policy applies to the Elsewhere <p>Kristian Cerni is the controller responsible for Elsewhere. This policy applies to the Elsewhere
website, account area, chats, persona market and related features.</p> website, account area, chats, persona market and related features.</p>
<dl class="legal-details"> <dl class="legal-details">
<div><dt>Service</dt><dd>Elsewhere</dd></div> <div>
<div><dt>Privacy contact</dt><dd><a href="mailto:kriscerni@gmail.com">kriscerni@gmail.com</a></dd></div> <dt>Service</dt>
<div><dt>Project contact</dt><dd><a href="https://github.com/KrisHD1337" rel="external noopener">github.com/KrisHD1337</a></dd></div> <dd>Elsewhere</dd>
</div>
<div>
<dt>Privacy contact</dt>
<dd><a href="mailto:kriscerni@gmail.com">kriscerni@gmail.com</a></dd>
</div>
<div>
<dt>Project contact</dt>
<dd><a href="https://github.com/KrisHD1337" rel="external noopener">github.com/KrisHD1337</a>
</dd>
</div>
</dl> </dl>
</section> </section>
@@ -69,18 +83,24 @@
<p>We collect data you provide and technical data generated while you use the service:</p> <p>We collect data you provide and technical data generated while you use the service:</p>
<ul> <ul>
<li><strong>Account data:</strong> username, password hash and session records. Elsewhere does not <li><strong>Account data:</strong> username, password hash and session records. Elsewhere does not
ask for an email address during registration.</li> ask for an email address during registration.
</li>
<li><strong>Workspace content:</strong> chats, AI responses, conversation titles, personas, prompt <li><strong>Workspace content:</strong> chats, AI responses, conversation titles, personas, prompt
templates, settings, memories, model choices, retry history and roleplay state.</li> templates, settings, memories, model choices, retry history and roleplay state.
</li>
<li><strong>Uploaded images:</strong> image files plus filename, type, size, dimensions, ownership, <li><strong>Uploaded images:</strong> image files plus filename, type, size, dimensions, ownership,
storage key, timestamps and cached analysis associated with your question.</li> storage key, timestamps and cached analysis associated with your question.
</li>
<li><strong>Persona market activity:</strong> published personas and creator username, collections, <li><strong>Persona market activity:</strong> published personas and creator username, collections,
favourites, ratings, reports and moderation records.</li> favourites, ratings, reports and moderation records.
</li>
<li><strong>Web-search data:</strong> the search query derived from your current message, selected <li><strong>Web-search data:</strong> the search query derived from your current message, selected
result links and snippets when you turn on web search.</li> result links and snippets when you turn on web search.
</li>
<li><strong>Technical and security data:</strong> session identifiers, request identifiers, routes, <li><strong>Technical and security data:</strong> session identifiers, request identifiers, routes,
response status and timing, account username for signed-in requests, and IP-based rate-limit response status and timing, account username for signed-in requests, and IP-based rate-limit
information held temporarily in memory.</li> information held temporarily in memory.
</li>
</ul> </ul>
<p>Please avoid putting sensitive personal data into chats or images unless it is genuinely necessary <p>Please avoid putting sensitive personal data into chats or images unless it is genuinely necessary
for your use of the service.</p> for your use of the service.</p>
@@ -90,19 +110,50 @@
<h2><span>03</span> How and why we use data</h2> <h2><span>03</span> How and why we use data</h2>
<div class="legal-table-wrap"> <div class="legal-table-wrap">
<table> <table>
<thead><tr><th>Purpose</th><th>Data used</th><th>Basis where the GDPR applies</th></tr></thead> <thead>
<tr>
<th>Purpose</th>
<th>Data used</th>
<th>Basis where the GDPR applies</th>
</tr>
</thead>
<tbody> <tbody>
<tr><td>Create and secure your account</td><td>Account, session and security data</td><td>Performance of our contract; legitimate interests in security</td></tr> <tr>
<tr><td>Provide chats, personas, uploads, exports and settings</td><td>Account and workspace content</td><td>Performance of our contract</td></tr> <td>Create and secure your account</td>
<tr><td>Run optional AI, image and search features</td><td>Your selected message, relevant context, images or query</td><td>Performance of our contract at your request</td></tr> <td>Account, session and security data</td>
<tr><td>Operate and moderate the persona market</td><td>Published content, username and market activity</td><td>Performance of our contract; legitimate interests in a safe public space</td></tr> <td>Performance of our contract; legitimate interests in security</td>
<tr><td>Prevent abuse, diagnose faults and maintain reliability</td><td>Technical, account and limited content data where necessary</td><td>Legitimate interests in security and service operation</td></tr> </tr>
<tr><td>Meet legal obligations and defend claims</td><td>Relevant records</td><td>Legal obligation or legitimate interests</td></tr> <tr>
<td>Provide chats, personas, uploads, exports and settings</td>
<td>Account and workspace content</td>
<td>Performance of our contract</td>
</tr>
<tr>
<td>Run optional AI, image and search features</td>
<td>Your selected message, relevant context, images or query</td>
<td>Performance of our contract at your request</td>
</tr>
<tr>
<td>Operate and moderate the persona market</td>
<td>Published content, username and market activity</td>
<td>Performance of our contract; legitimate interests in a safe public space</td>
</tr>
<tr>
<td>Prevent abuse, diagnose faults and maintain reliability</td>
<td>Technical, account and limited content data where necessary</td>
<td>Legitimate interests in security and service operation</td>
</tr>
<tr>
<td>Meet legal obligations and defend claims</td>
<td>Relevant records</td>
<td>Legal obligation or legitimate interests</td>
</tr>
</tbody> </tbody>
</table> </table>
</div> </div>
<p>We do not use your data for targeted advertising and do not sell it. Elsewhere does not make <p>We do not use your data for targeted advertising and do not sell it. Elsewhere does not make
decisions about you that produce legal or similarly significant effects solely by automated means.</p> decisions about you that produce legal or similarly significant effects solely by automated
means.</p>
</section> </section>
<section id="providers"> <section id="providers">
@@ -134,9 +185,12 @@
<p>We disclose personal data only as needed:</p> <p>We disclose personal data only as needed:</p>
<ul> <ul>
<li>to the configured model and search services when you use those features;</li> <li>to the configured model and search services when you use those features;</li>
<li>to infrastructure providers that host or support the application under appropriate safeguards;</li> <li>to infrastructure providers that host or support the application under appropriate safeguards;
</li>
<li>to other users when you intentionally publish a persona;</li> <li>to other users when you intentionally publish a persona;</li>
<li>when required by law, a valid legal process or to protect rights, safety and service security; or</li> <li>when required by law, a valid legal process or to protect rights, safety and service security;
or
</li>
<li>as part of a service reorganisation, with notice and appropriate protection where required.</li> <li>as part of a service reorganisation, with notice and appropriate protection where required.</li>
</ul> </ul>
</section> </section>
@@ -152,14 +206,23 @@
<section id="retention"> <section id="retention">
<h2><span>08</span> How long data is kept</h2> <h2><span>08</span> How long data is kept</h2>
<ul> <ul>
<li><strong>Account and workspace data</strong> remain until you delete individual items or your account.</li> <li><strong>Account and workspace data</strong> remain until you delete individual items or your
<li><strong>Temporary, unattached uploads</strong> are scheduled for deletion after 24 hours. Images attached to a message remain with that conversation.</li> account.
<li><strong>Session records</strong> expire according to the configured session lifetime and can be invalidated from Settings.</li> </li>
<li><strong>Operational logs and in-memory rate-limit records</strong> are kept only as long as reasonably needed for security, diagnosis and operation.</li> <li><strong>Temporary, unattached uploads</strong> are scheduled for deletion after 24 hours. Images
attached to a message remain with that conversation.
</li>
<li><strong>Session records</strong> expire according to the configured session lifetime and can be
invalidated from Settings.
</li>
<li><strong>Operational logs and in-memory rate-limit records</strong> are kept only as long as
reasonably needed for security, diagnosis and operation.
</li>
</ul> </ul>
<p>Deleting your account removes its workspace, attached files and published market entries from the <p>Deleting your account removes its workspace, attached files and published market entries from the
active service. Deletion from backups or queued file cleanup may take additional time. We may keep active service. Deletion from backups or queued file cleanup may take additional time. We may keep
limited records longer where required by law or necessary to establish, exercise or defend legal claims.</p> limited records longer where required by law or necessary to establish, exercise or defend legal
claims.</p>
</section> </section>
<section id="transfers"> <section id="transfers">
@@ -201,14 +264,14 @@
</section> </section>
</article> </article>
</div> </div>
</main> </main>
<footer class="legal-footer"> <footer class="legal-footer">
<span>Elsewhere</span> <span>Elsewhere</span>
<a href="/tos.html">Terms of Service</a> <a href="/tos.html">Terms of Service</a>
<a href="/">Back to Elsewhere</a> <a href="/">Back to Elsewhere</a>
</footer> </footer>
<script type="module" src="/js/workspaceNavigation.js?v=20260923-legal"></script> <script type="module" src="/js/workspaceNavigation.js?v=20260923-legal"></script>
</body> </body>
</html> </html>
+34 -27
View File
@@ -3,7 +3,7 @@
<head> <head>
<link rel="icon" type="image/svg+xml" href="/img/favicon.svg"> <link rel="icon" type="image/svg+xml" href="/img/favicon.svg">
<meta charset="UTF-8" /> <meta charset="UTF-8"/>
<title>Elsewhere Settings</title> <title>Elsewhere Settings</title>
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
@@ -11,14 +11,14 @@
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap" href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Manrope:wght@400;500;600;700;800&display=swap"
rel="stylesheet"> rel="stylesheet">
<script src="/js/theme-bootstrap.js?v=20260909-account-controls"></script> <script src="/js/theme-bootstrap.js?v=20260909-account-controls"></script>
<link rel="stylesheet" href="/css/style.css?v=20260909-account-controls" /> <link rel="stylesheet" href="/css/style.css?v=20260909-account-controls"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/css/shell.css?v=20260909-account-controls"> <link rel="stylesheet" href="/css/shell.css?v=20260909-account-controls">
</head> </head>
<body class="site-page settings-page" data-theme="fakegpt" data-theme-mode="light"> <body class="site-page settings-page" data-theme="fakegpt" data-theme-mode="light">
<!-- site-header --> <!-- site-header -->
<main id="main" class="settings-shell page-content"> <main id="main" class="settings-shell page-content">
<header class="market-header market-header-simple"> <header class="market-header market-header-simple">
<div> <div>
<p class="eyebrow">Elsewhere settings</p> <p class="eyebrow">Elsewhere settings</p>
@@ -31,12 +31,15 @@
<section class="settings-layout"> <section class="settings-layout">
<aside class="settings-nav" aria-label="Settings sections" role="tablist"> <aside class="settings-nav" aria-label="Settings sections" role="tablist">
<button class="settings-nav-item active" type="button" role="tab" aria-selected="true" <button class="settings-nav-item active" type="button" role="tab" aria-selected="true"
data-settings-view="personal">Account</button> data-settings-view="personal">Account
</button>
<button class="settings-nav-item" type="button" role="tab" aria-selected="false" <button class="settings-nav-item" type="button" role="tab" aria-selected="false"
data-settings-view="design">Appearance</button> data-settings-view="design">Appearance
</button>
<button id="diagnosticsNavItem" class="settings-nav-item hidden" type="button" role="tab" <button id="diagnosticsNavItem" class="settings-nav-item hidden" type="button" role="tab"
aria-selected="false" data-settings-view="diagnostics">Diagnostics</button> aria-selected="false" data-settings-view="diagnostics">Diagnostics
</button>
</aside> </aside>
<div class="settings-content"> <div class="settings-content">
@@ -51,15 +54,15 @@
<form id="usernameForm" class="settings-form-stack"> <form id="usernameForm" class="settings-form-stack">
<label class="field"> <label class="field">
<span>Current username</span> <span>Current username</span>
<input id="currentUsername" readonly /> <input id="currentUsername" readonly/>
</label> </label>
<label class="field"> <label class="field">
<span>New username</span> <span>New username</span>
<input id="newUsername" autocomplete="username" /> <input id="newUsername" autocomplete="username"/>
</label> </label>
<label class="field"> <label class="field">
<span>Current password</span> <span>Current password</span>
<input id="usernamePassword" type="password" autocomplete="current-password" /> <input id="usernamePassword" type="password" autocomplete="current-password"/>
</label> </label>
<button id="saveUsername" type="submit">Save username</button> <button id="saveUsername" type="submit">Save username</button>
</form> </form>
@@ -75,15 +78,15 @@
<form id="passwordForm" class="settings-form-stack"> <form id="passwordForm" class="settings-form-stack">
<label class="field"> <label class="field">
<span>Current password</span> <span>Current password</span>
<input id="currentPassword" type="password" autocomplete="current-password" /> <input id="currentPassword" type="password" autocomplete="current-password"/>
</label> </label>
<label class="field"> <label class="field">
<span>New password</span> <span>New password</span>
<input id="newPassword" type="password" autocomplete="new-password" /> <input id="newPassword" type="password" autocomplete="new-password"/>
</label> </label>
<label class="field"> <label class="field">
<span>Repeat new password</span> <span>Repeat new password</span>
<input id="repeatPassword" type="password" autocomplete="new-password" /> <input id="repeatPassword" type="password" autocomplete="new-password"/>
</label> </label>
<button id="savePassword" type="submit">Save password</button> <button id="savePassword" type="submit">Save password</button>
</form> </form>
@@ -101,7 +104,8 @@
<div class="persona-actions"> <div class="persona-actions">
<button id="exportAccount" type="button" class="secondary-action">Export account</button> <button id="exportAccount" type="button" class="secondary-action">Export account</button>
<button id="logoutAllSessions" type="button" class="secondary-action">Logout all <button id="logoutAllSessions" type="button" class="secondary-action">Logout all
sessions</button> sessions
</button>
</div> </div>
</div> </div>
@@ -116,7 +120,8 @@
</div> </div>
<div class="persona-actions"> <div class="persona-actions">
<button id="deleteAccount" type="button" class="secondary-action danger">Delete <button id="deleteAccount" type="button" class="secondary-action danger">Delete
account</button> account
</button>
</div> </div>
</div> </div>
</section> </section>
@@ -128,7 +133,8 @@
<div> <div>
<p class="eyebrow">Curated assistants</p> <p class="eyebrow">Curated assistants</p>
<h2>Personalities</h2> <h2>Personalities</h2>
<p class="subtitle">Choose a focused assistant for writing, coding, research, learning, or planning.</p> <p class="subtitle">Choose a focused assistant for writing, coding, research, learning, or
planning.</p>
</div> </div>
<a class="secondary-action" href="/app/personas">Browse Personalities</a> <a class="secondary-action" href="/app/personas">Browse Personalities</a>
</div> </div>
@@ -221,10 +227,11 @@
</section> </section>
</div> </div>
</section> </section>
</main> </main>
<div id="settingsPersonaModal" class="modal-overlay hidden"> <div id="settingsPersonaModal" class="modal-overlay hidden">
<div class="modal-card persona-modal-card" role="dialog" aria-modal="true" aria-labelledby="settingsPersonaFormTitle" tabindex="-1"> <div class="modal-card persona-modal-card" role="dialog" aria-modal="true"
aria-labelledby="settingsPersonaFormTitle" tabindex="-1">
<div class="modal-header"> <div class="modal-header">
<div class="persona-modal-heading"> <div class="persona-modal-heading">
<p class="eyebrow">Persona editor</p> <p class="eyebrow">Persona editor</p>
@@ -247,11 +254,11 @@
</label> </label>
<label class="field"> <label class="field">
<span>Name</span> <span>Name</span>
<input id="settingsPersonaName" placeholder="Persona name" required /> <input id="settingsPersonaName" placeholder="Persona name" required/>
</label> </label>
<label class="field"> <label class="field">
<span>Pronouns</span> <span>Pronouns</span>
<input id="settingsPersonaPronouns" placeholder="e.g. she/her, they/them" /> <input id="settingsPersonaPronouns" placeholder="e.g. she/her, they/them"/>
</label> </label>
<label class="field field-wide"> <label class="field field-wide">
<span>Appearance</span> <span>Appearance</span>
@@ -284,9 +291,9 @@
</div> </div>
</form> </form>
</div> </div>
</div> </div>
<div id="settingsPopupModal" class="modal-overlay hidden"> <div id="settingsPopupModal" class="modal-overlay hidden">
<div class="modal-card popup-card" role="dialog" aria-modal="true" aria-labelledby="settingsPopupTitle" <div class="modal-card popup-card" role="dialog" aria-modal="true" aria-labelledby="settingsPopupTitle"
aria-describedby="settingsPopupDescription"> aria-describedby="settingsPopupDescription">
<div class="modal-header"> <div class="modal-header">
@@ -300,7 +307,7 @@
<p id="settingsPopupDescription" class="subtitle">Please confirm this action.</p> <p id="settingsPopupDescription" class="subtitle">Please confirm this action.</p>
<label id="settingsPopupField" class="field"> <label id="settingsPopupField" class="field">
<span id="settingsPopupInputLabel">Value</span> <span id="settingsPopupInputLabel">Value</span>
<input id="settingsPopupInput" type="text" autocomplete="off" /> <input id="settingsPopupInput" type="text" autocomplete="off"/>
</label> </label>
</div> </div>
<div class="popup-actions"> <div class="popup-actions">
@@ -308,9 +315,9 @@
<button id="settingsPopupConfirm" type="button">Confirm</button> <button id="settingsPopupConfirm" type="button">Confirm</button>
</div> </div>
</div> </div>
</div> </div>
<script type="module" src="/js/settings.js?v=20260930-account-fixes"></script> <script type="module" src="/js/settings.js?v=20260930-account-fixes"></script>
<script type="module" src="/js/workspaceNavigation.js?v=20260909-account-controls"></script> <script type="module" src="/js/workspaceNavigation.js?v=20260909-account-controls"></script>
</body> </body>
</html> </html>
+15 -10
View File
@@ -17,14 +17,16 @@
</head> </head>
<body class="site-page legal-page"> <body class="site-page legal-page">
<!-- site-header --> <!-- site-header -->
<main id="main" class="legal-shell"> <main id="main" class="legal-shell">
<header class="legal-hero"> <header class="legal-hero">
<p class="eyebrow">The ground rules</p> <p class="eyebrow">The ground rules</p>
<h1>Terms of Service</h1> <h1>Terms of Service</h1>
<p class="legal-summary">Please read these terms before using Elsewhere. They explain what you can expect <p class="legal-summary">Please read these terms before using Elsewhere. They explain what you can expect
from the service and what we expect from you.</p> from the service and what we expect from you.</p>
<p class="legal-updated">Effective and last updated <time datetime="2026-09-23">23 September 2026</time></p> <p class="legal-updated">Effective and last updated
<time datetime="2026-09-23">23 September 2026</time>
</p>
</header> </header>
<div class="legal-layout"> <div class="legal-layout">
@@ -99,9 +101,11 @@
<li>access another person’s account or data without permission;</li> <li>access another person’s account or data without permission;</li>
<li>misrepresent AI-generated material as authentic where that could deceive or harm someone;</li> <li>misrepresent AI-generated material as authentic where that could deceive or harm someone;</li>
<li>publish content that is unlawful, abusive, exploitative or infringes intellectual-property or <li>publish content that is unlawful, abusive, exploitative or infringes intellectual-property or
privacy rights; or</li> privacy rights; or
</li>
<li>automate access in a way that places an unreasonable load on the service or circumvents its <li>automate access in a way that places an unreasonable load on the service or circumvents its
limits.</li> limits.
</li>
</ul> </ul>
<p>We may remove public content or restrict access when reasonably necessary to address abuse, <p>We may remove public content or restrict access when reasonably necessary to address abuse,
security risks, legal obligations or violations of these Terms.</p> security risks, legal obligations or violations of these Terms.</p>
@@ -139,7 +143,8 @@
<h2><span>09</span> Suspension and termination</h2> <h2><span>09</span> Suspension and termination</h2>
<p>You may stop using Elsewhere or delete your account at any time. We may suspend or terminate <p>You may stop using Elsewhere or delete your account at any time. We may suspend or terminate
access if you materially or repeatedly violate these Terms, create a security or legal risk, or access if you materially or repeatedly violate these Terms, create a security or legal risk, or
misuse the service. Where reasonable, we will give notice and an opportunity to address the issue.</p> misuse the service. Where reasonable, we will give notice and an opportunity to address the
issue.</p>
</section> </section>
<section id="law"> <section id="law">
@@ -161,14 +166,14 @@
</section> </section>
</article> </article>
</div> </div>
</main> </main>
<footer class="legal-footer"> <footer class="legal-footer">
<span>Elsewhere</span> <span>Elsewhere</span>
<a href="/privacy.html">Privacy Policy</a> <a href="/privacy.html">Privacy Policy</a>
<a href="/">Back to Elsewhere</a> <a href="/">Back to Elsewhere</a>
</footer> </footer>
<script type="module" src="/js/workspaceNavigation.js?v=20260923-legal"></script> <script type="module" src="/js/workspaceNavigation.js?v=20260923-legal"></script>
</body> </body>
</html> </html>
+14 -7
View File
@@ -1,9 +1,9 @@
const entries = [ const entries = [
{ {
id: "general-assistant", name: "Leonardo", specialization: "General Assistance", id: "general-assistant", name: "Leonardo", specialization: "General Assistance",
description: "A versatile partner for everyday questions, ideas, and decisions.", description: "A versatile partner for everyday questions.",
longDescription: "Leonardo connects ideas across subjects and turns them into clear, practical help. Use this personality for everyday questions, explanations, conversations, brainstorming, and decisions that span more than one field.", longDescription: "Leonardo connects ideas across subjects and turns them into clear, practical help. Use this personality for everyday questions, explanations, conversations, brainstorming, and decisions that span more than one field.",
historicalInspiration: "Leonardo da Vinci", icon: "spark", accentColor: "#168b87", isDefault: true, historicalInspiration: "Leonardo da Vinci", icon: "spark", accentColor: "#168b87",
systemPrompt: `You are Elsewhere's versatile general assistant. Your purpose is to help users understand, decide, create, plan, and get everyday work done across a wide range of subjects. Be curious, practical, balanced, and adaptable rather than forcing every request into one specialty. Connect ideas across disciplines when the connection makes the answer clearer or more useful; do not add tangents simply to demonstrate breadth. systemPrompt: `You are Elsewhere's versatile general assistant. Your purpose is to help users understand, decide, create, plan, and get everyday work done across a wide range of subjects. Be curious, practical, balanced, and adaptable rather than forcing every request into one specialty. Connect ideas across disciplines when the connection makes the answer clearer or more useful; do not add tangents simply to demonstrate breadth.
First identify what the user is actually trying to accomplish and answer that need directly. For straightforward questions, lead with the answer and keep it concise. For complex questions, organize the explanation into a useful sequence, define unfamiliar terms, and include examples or next steps when they improve understanding. Explain technical material in plain language without removing important nuance. Match the user's level of detail, tone, and requested format. First identify what the user is actually trying to accomplish and answer that need directly. For straightforward questions, lead with the answer and keep it concise. For complex questions, organize the explanation into a useful sequence, define unfamiliar terms, and include examples or next steps when they improve understanding. Explain technical material in plain language without removing important nuance. Match the user's level of detail, tone, and requested format.
@@ -14,7 +14,7 @@ Keep claims grounded. Distinguish known information from inference, acknowledge
}, },
{ {
id: "creative-writer", name: "Shakespeare", specialization: "Creative Writing", id: "creative-writer", name: "Shakespeare", specialization: "Creative Writing",
description: "Develop stories, build worlds, and bring characters to life.", description: "Develop stories, worlds and characters.",
longDescription: "Shakespeare helps with fiction, worldbuilding, character arcs, dialogue, atmosphere, and narrative editing. It follows your chosen genre and voice without assuming an archaic style or imitating its historical inspiration.", longDescription: "Shakespeare helps with fiction, worldbuilding, character arcs, dialogue, atmosphere, and narrative editing. It follows your chosen genre and voice without assuming an archaic style or imitating its historical inspiration.",
historicalInspiration: "William Shakespeare", icon: "quill", accentColor: "#7957b5", historicalInspiration: "William Shakespeare", icon: "quill", accentColor: "#7957b5",
systemPrompt: `You are Elsewhere's specialized creative-writing partner. Your work includes fiction, short stories, novels, scenes, dialogue, worldbuilding, characters, plot development, and editorial feedback. Prioritize imaginative choices, narrative cohesion, emotional specificity, and language that feels intentional rather than mechanically polished. systemPrompt: `You are Elsewhere's specialized creative-writing partner. Your work includes fiction, short stories, novels, scenes, dialogue, worldbuilding, characters, plot development, and editorial feedback. Prioritize imaginative choices, narrative cohesion, emotional specificity, and language that feels intentional rather than mechanically polished.
@@ -27,7 +27,7 @@ Track continuity across the conversation, including names, relationships, chrono
}, },
{ {
id: "programmer", name: "Ada", specialization: "Programming & Development", id: "programmer", name: "Ada", specialization: "Programming & Development",
description: "Design, debug, and improve software with precision and care.", description: "Create and debug software.",
longDescription: "Ada works through software problems systematically, from root-cause debugging to architecture and implementation. It favors maintainable, working solutions that fit your codebase and avoids complexity that does not solve a real problem.", longDescription: "Ada works through software problems systematically, from root-cause debugging to architecture and implementation. It favors maintainable, working solutions that fit your codebase and avoids complexity that does not solve a real problem.",
historicalInspiration: "Ada Lovelace", icon: "code", accentColor: "#3478c8", historicalInspiration: "Ada Lovelace", icon: "code", accentColor: "#3478c8",
systemPrompt: `You are Elsewhere's precise, systematic software engineering specialist. Help with implementation, debugging, code review, architecture, refactoring, testing, and technical explanations. Optimize for correctness, maintainability, security, and compatibility with the actual repository, language, framework, runtime, and project conventions—not for an abstract idealized solution. systemPrompt: `You are Elsewhere's precise, systematic software engineering specialist. Help with implementation, debugging, code review, architecture, refactoring, testing, and technical explanations. Optimize for correctness, maintainability, security, and compatibility with the actual repository, language, framework, runtime, and project conventions—not for an abstract idealized solution.
@@ -53,7 +53,7 @@ When sources conflict, compare their evidence, methods, definitions, incentives,
}, },
{ {
id: "tutor", name: "Socrates", specialization: "Learning & Education", id: "tutor", name: "Socrates", specialization: "Learning & Education",
description: "Understand new ideas through clear explanations and useful practice.", description: "Understand new ideas through clear explanations.",
longDescription: "Socrates is a patient, engaging tutor who adapts to your level and makes difficult ideas easier to work through. It uses examples and targeted questions to build understanding, while giving a direct answer whenever you ask for one.", longDescription: "Socrates is a patient, engaging tutor who adapts to your level and makes difficult ideas easier to work through. It uses examples and targeted questions to build understanding, while giving a direct answer whenever you ask for one.",
historicalInspiration: "Socrates", icon: "book-open", accentColor: "#b88120", historicalInspiration: "Socrates", icon: "book-open", accentColor: "#b88120",
systemPrompt: `You are Elsewhere's patient, adaptive tutor. Your purpose is to build the learner's understanding, confidence, and ability to use an idea independently—not merely to deliver facts. Be intellectually engaging, respectful, and encouraging without being patronizing or artificially praising routine effort. systemPrompt: `You are Elsewhere's patient, adaptive tutor. Your purpose is to build the learner's understanding, confidence, and ability to use an idea independently—not merely to deliver facts. Be intellectually engaging, respectful, and encouraging without being patronizing or artificially praising routine effort.
@@ -81,7 +81,11 @@ Present the reasoning in a readable order: frame the problem, identify decisive
const registry = new Map(entries.map(entry => [entry.id, Object.freeze(entry)])); const registry = new Map(entries.map(entry => [entry.id, Object.freeze(entry)]));
export const DEFAULT_PERSONALITY_ID = "general-assistant"; export const DEFAULT_PERSONALITY_ID = "general-assistant";
export function getPersonality(id) { return registry.get(id) || null; }
export function getPersonality(id) {
return registry.get(id) || null;
}
export function listPersonalities() { export function listPersonalities() {
return entries.map(entry => ({ return entries.map(entry => ({
id: entry.id, id: entry.id,
@@ -95,4 +99,7 @@ export function listPersonalities() {
...(entry.isDefault ? {isDefault: true} : {}) ...(entry.isDefault ? {isDefault: true} : {})
})); }));
} }
export function getPersonalitySystemPrompt(id) { return getPersonality(id)?.systemPrompt || registry.get(DEFAULT_PERSONALITY_ID).systemPrompt; }
export function getPersonalitySystemPrompt(id) {
return getPersonality(id)?.systemPrompt || registry.get(DEFAULT_PERSONALITY_ID).systemPrompt;
}