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>
<body class="site-page account-page">
<!-- site-header -->
<main id="main" class="account-layout page-content">
<!-- site-header -->
<main id="main" class="account-layout page-content">
<section class="account-intro">
<p class="eyebrow">Welcome to elsewhere</p>
<h1 id="accountHeadline">Pick up where<br>curiosity left off.</h1>
@@ -43,9 +43,9 @@
aria-pressed="false">
<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">
<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" />
<path class="eye-slash" d="m3 3 18 18" />
<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"/>
<path class="eye-slash" d="m3 3 18 18"/>
</svg>
</button>
</div>
@@ -63,8 +63,8 @@
aria-atomic="true" tabindex="-1">
<svg class="account-error-icon" viewBox="0 0 24 24" width="20" height="20" fill="none"
stroke="currentColor" stroke-width="2" aria-hidden="true">
<circle cx="12" cy="12" r="9" />
<path d="M12 7v6m0 3v1" />
<circle cx="12" cy="12" r="9"/>
<path d="M12 7v6m0 3v1"/>
</svg>
<span id="accountStatusText"></span>
</div>
@@ -73,16 +73,16 @@
<p id="accountSwitch">
</p>
</section>
</main>
<footer class="site-footer">
</main>
<footer class="site-footer">
<a class="site-brand" href="/">elsewhere</a>
<span>A space for every conversation.</span>
<a 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/workspaceNavigation.js?v=20260909-account-controls"></script>
</footer>
<script type="module" src="/js/pages.js?v=20260909-account-controls">
</script>
<script type="module" src="/js/workspaceNavigation.js?v=20260909-account-controls"></script>
</body>
</html>
+117 -69
View File
@@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta charset="UTF-8"/>
<link rel="icon" type="image/svg+xml" href="/img/favicon.svg">
<title>Elsewhere</title>
<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"
rel="stylesheet">
<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">
<link rel="stylesheet" href="/css/pages.css?v=20260909-account-controls">
<link rel="stylesheet" href="/css/shell.css?v=20260909-account-controls">
</head>
<body class="site-page chat-page" data-theme="fakegpt" data-theme-mode="light">
<!-- site-header -->
<div class="app-shell">
<!-- site-header -->
<div class="app-shell">
<section id="chat" class="card chat-card chat-focus-card hidden" aria-label="AI chat workspace">
<div class="chat-layout chat-layout-focus">
<aside id="chatSidebar" class="chat-sidebar" aria-label="Conversation history">
<div class="sidebar-top">
<div class="sidebar-workspace-card">
<h2>Conversations</h2><button id="newChat" class="primary-action" type="button">New
chat</button>
<h2>Conversations</h2>
<button id="newChat" class="primary-action" type="button">New
chat
</button>
</div>
<label class="field compact-field">
<span>Search</span>
<input id="chatSearch" type="search" placeholder="Search chats" />
<input id="chatSearch" type="search" placeholder="Search chats"/>
</label>
</div>
<div class="sidebar-middle">
@@ -50,7 +52,8 @@
<header class="chat-header chat-header-focus">
<button id="chatSidebarToggle" class="icon-button sidebar-toggle" type="button"
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">
<h2 id="activeChatTitle">New chat</h2>
@@ -61,7 +64,8 @@
aria-controls="chatDrawer"
aria-label="Change AI model"><span>Model</span><strong
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 class="chat-header-actions">
@@ -79,7 +83,8 @@
<h2 id="chatDrawerTitle">Settings</h2>
</div>
<button id="chatDrawerClose" class="icon-button" type="button"
aria-label="Close chat settings">×</button>
aria-label="Close chat settings">×
</button>
</div>
<div class="chat-drawer-body">
<div class="chat-drawer-summary">
@@ -131,23 +136,26 @@
</div>
</details>
<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">
<label class="field"><span>Temperature</span><input
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
id="chatContextLength" type="number" min="256"
max="131072" placeholder="Default" /></label>
max="131072" placeholder="Default"/></label>
<label class="field"><span>Response length</span><input
id="chatResponseLength" type="number" min="1"
max="32768" placeholder="Default" /></label>
max="32768" placeholder="Default"/></label>
<label class="field field-wide"><span>System
instruction</span><textarea
id="chatSystemInstruction" rows="4" maxlength="8000"
placeholder="Optional instructions for this chat"></textarea></label>
<button id="saveGenerationSettings" type="button">Save
generation settings</button>
generation settings
</button>
</div>
</details>
<div class="chat-drawer-section">
@@ -155,15 +163,19 @@
<div class="chat-drawer-action-list">
<button id="renameChat"
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"
type="button" data-chat-action="pin">Pin</button>
type="button" data-chat-action="pin">Pin
</button>
<button id="archiveChat"
class="secondary-action chat-drawer-action" type="button"
data-chat-action="archive">Archive</button>
data-chat-action="archive">Archive
</button>
<button id="exportChat"
class="secondary-action chat-drawer-action" type="button"
data-chat-action="export">Export</button>
data-chat-action="export">Export
</button>
</div>
</div>
<details class="chat-drawer-section chat-drawer-more">
@@ -176,27 +188,37 @@
<div class="chat-drawer-action-list chat-drawer-disclosure-panel">
<button id="moveChatFolder"
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"
class="secondary-action chat-drawer-action" type="button"
data-chat-action="memory">Conversation memory</button>
<button id="exploreBranches" type="button" class="secondary-action chat-drawer-action" data-chat-action="branches">Branches &amp; compare</button>
data-chat-action="memory">Conversation memory
</button>
<button id="exploreBranches" type="button"
class="secondary-action chat-drawer-action"
data-chat-action="branches">Branches &amp; compare
</button>
<button id="managePersonaState"
class="secondary-action chat-drawer-action" type="button"
data-chat-action="personaState">Roleplay state</button>
data-chat-action="personaState">Roleplay state
</button>
</div>
</details>
<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">
<button id="backupWorkspace"
class="secondary-action chat-drawer-action"
type="button">Download workspace archive</button>
type="button">Download workspace archive
</button>
<button id="importWorkspace"
class="secondary-action chat-drawer-action"
type="button">Import workspace</button>
type="button">Import workspace
</button>
<input id="importWorkspaceFile" class="hidden" type="file"
accept="application/json,.json,.tar.gz,application/gzip" />
accept="application/json,.json,.tar.gz,application/gzip"/>
</div>
</details>
<details class="chat-drawer-section chat-drawer-danger">
@@ -209,7 +231,8 @@
<div class="chat-drawer-action-list chat-drawer-disclosure-panel">
<button id="clearChat"
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>
</details>
</div>
@@ -236,20 +259,35 @@
<div class="messages-wrap">
<div class="history-controls">
<button id="loadOlderMessages" class="secondary-action" type="button" hidden>Load older messages</button>
<button id="loadLatestMessages" class="secondary-action" type="button" hidden>Back to latest messages</button>
<button id="loadOlderMessages" class="secondary-action" type="button" hidden>Load older
messages
</button>
<button id="loadLatestMessages" class="secondary-action" type="button" hidden>Back to latest
messages
</button>
</div>
<button id="conversationSearchToggle" class="conversation-search-toggle secondary-action" 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">
<button id="conversationSearchToggle" class="conversation-search-toggle secondary-action"
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>
<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>
<button id="conversationSearchPrevious" class="secondary-action" type="button" aria-label="Previous match" disabled>Previous</button>
<button id="conversationSearchNext" class="secondary-action" type="button" aria-label="Next match" disabled>Next</button>
<button id="conversationSearchPrevious" class="secondary-action" type="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 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>
<button id="jumpToLatest" class="jump-to-latest secondary-action hidden" type="button">Jump to latest</button>
<div id="replyAnnouncement" class="visually-hidden" role="status" aria-live="polite"
aria-atomic="true"></div>
<button id="jumpToLatest" class="jump-to-latest secondary-action hidden" type="button">Jump to
latest
</button>
</div>
<div class="input-shell input-shell-focus">
@@ -264,19 +302,23 @@
title="Attach image">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1.6" aria-hidden="true">
<rect x="3" y="3" width="18" height="18" rx="3" />
<circle cx="8" cy="8" r="1.5" />
<path d="m3 17 5-5 4 4 4-6 5 7" />
</svg><span class="composer-tool-label">Attach</span>
<rect x="3" y="3" width="18" height="18" rx="3"/>
<circle cx="8" cy="8" r="1.5"/>
<path d="m3 17 5-5 4 4 4-6 5 7"/>
</svg>
<span class="composer-tool-label">Attach</span>
</button>
<button id="webSearch" class="icon-button" type="button" 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">
<button id="webSearch" class="icon-button" type="button"
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"
stroke-width="1.6" aria-hidden="true">
<circle cx="12" cy="12" r="9" />
<ellipse cx="12" cy="12" rx="4" ry="9" />
<path d="M3 12h18" />
</svg><span class="composer-tool-label">Web search</span>
<circle cx="12" cy="12" r="9"/>
<ellipse cx="12" cy="12" rx="4" ry="9"/>
<path d="M3 12h18"/>
</svg>
<span class="composer-tool-label">Web search</span>
</button>
<button id="reasoning" class="icon-button" type="button" aria-label="Enable reasoning"
title="Enable reasoning" aria-pressed="false" disabled hidden>
@@ -284,22 +326,26 @@
stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"
aria-hidden="true">
<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" />
</svg><span class="composer-tool-label">Reasoning</span>
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>
</button>
<label class="visually-hidden" for="msgInput">Message</label>
<textarea id="msgInput" rows="1" placeholder="Type your message..."></textarea>
<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>
</section>
</div>
</section>
</div>
</div>
<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 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-header">
<div class="persona-modal-heading">
<p class="eyebrow">Persona editor</p>
@@ -323,11 +369,11 @@
</label>
<label class="field">
<span>Persona name</span>
<input id="personaName" placeholder="Persona name" required />
<input id="personaName" placeholder="Persona name" required/>
</label>
<label class="field">
<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 class="field field-wide">
<span>Appearance</span>
@@ -360,10 +406,11 @@
</div>
</form>
</div>
</div>
</div>
<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 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-header">
<div class="persona-modal-heading">
<p class="eyebrow">Start roleplay</p>
@@ -405,9 +452,9 @@
<button id="roleplayStarterConfirm" type="button">Continue</button>
</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"
aria-describedby="popupDescription">
<div class="modal-header">
@@ -421,7 +468,7 @@
<p id="popupDescription" class="subtitle">Please confirm this action.</p>
<label id="popupField" class="field hidden">
<span id="popupInputLabel">Value</span>
<input id="popupInput" type="text" autocomplete="off" />
<input id="popupInput" type="text" autocomplete="off"/>
</label>
</div>
<div class="popup-actions">
@@ -429,10 +476,11 @@
<button id="popupConfirm" type="button">Confirm</button>
</div>
</div>
</div>
</div>
<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 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-header">
<div class="persona-modal-heading">
<p class="eyebrow">Get started</p>
@@ -447,12 +495,12 @@
<button id="onboardingContinue" type="button">Continue</button>
</div>
</div>
</div>
</div>
<script src="/vendor/marked.min.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/workspaceNavigation.js?v=20260909-account-controls"></script>
<script src="/vendor/marked.min.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/workspaceNavigation.js?v=20260909-account-controls"></script>
</body>
</html>
+6 -6
View File
@@ -17,9 +17,9 @@
</head>
<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>
<h1>What would you like to do?</h1>
<p class="hero-copy">Start somewhere new, or pick up a conversation.</p>
@@ -52,11 +52,11 @@
</section>
<p id="homeStatus" role="status">
</p>
</main>
</main>
<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/pages.js?v=20260909-account-controls">
</script>
<script type="module" src="/js/workspaceNavigation.js?v=20260909-account-controls"></script>
</body>
</html>
+6 -6
View File
@@ -16,8 +16,8 @@
</head>
<body class="site-page landing-page">
<!-- site-header -->
<main id="main" class="landing-entry">
<!-- site-header -->
<main id="main" class="landing-entry">
<div class="entry-door" aria-hidden="true"><span></span></div>
<div class="entry-copy">
<p class="eyebrow">A space for every conversation.</p>
@@ -28,11 +28,11 @@
aria-hidden="true">&rarr;</span></a>
</div>
</div>
</main>
<footer class="site-footer"><span>elsewhere / a little room for imagination</span><a
</main>
<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>
<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/pages.js?v=20260909-account-controls"></script>
<script type="module" src="/js/workspaceNavigation.js?v=20260909-account-controls"></script>
</body>
</html>
+25 -2
View File
@@ -1,3 +1,26 @@
<!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>
<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>
<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>
<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>
<body class="site-page legal-page">
<!-- site-header -->
<main id="main" class="legal-shell">
<!-- site-header -->
<main id="main" class="legal-shell">
<header class="legal-hero">
<p class="eyebrow">Your data, explained</p>
<h1>Privacy Policy</h1>
<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>
<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>
<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>You stay in control</strong><span>Export your workspace or delete your account in Settings.</span></div>
<div><strong>Features are optional</strong><span>Web search, image uploads and public publishing are choices you make.</span></div>
<div><strong>You stay in control</strong><span>Export your workspace or delete your account in Settings.</span>
</div>
<div><strong>Features are optional</strong><span>Web search, image uploads and public publishing are choices you make.</span>
</div>
</aside>
<div class="legal-layout">
@@ -58,9 +62,19 @@
<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>
<dl class="legal-details">
<div><dt>Service</dt><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>
<div>
<dt>Service</dt>
<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>
</section>
@@ -69,18 +83,24 @@
<p>We collect data you provide and technical data generated while you use the service:</p>
<ul>
<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
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,
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,
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
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,
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>
<p>Please avoid putting sensitive personal data into chats or images unless it is genuinely necessary
for your use of the service.</p>
@@ -90,19 +110,50 @@
<h2><span>03</span> How and why we use data</h2>
<div class="legal-table-wrap">
<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>
<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><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>
<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>
<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>
</table>
</div>
<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 id="providers">
@@ -134,9 +185,12 @@
<p>We disclose personal data only as needed:</p>
<ul>
<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>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>
</ul>
</section>
@@ -152,14 +206,23 @@
<section id="retention">
<h2><span>08</span> How long data is kept</h2>
<ul>
<li><strong>Account and workspace data</strong> remain until you delete individual items or your account.</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>
<li><strong>Account and workspace data</strong> remain until you delete individual items or your
account.
</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>
<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
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 id="transfers">
@@ -201,14 +264,14 @@
</section>
</article>
</div>
</main>
</main>
<footer class="legal-footer">
<footer class="legal-footer">
<span>Elsewhere</span>
<a href="/tos.html">Terms of Service</a>
<a href="/">Back to Elsewhere</a>
</footer>
<script type="module" src="/js/workspaceNavigation.js?v=20260923-legal"></script>
</footer>
<script type="module" src="/js/workspaceNavigation.js?v=20260923-legal"></script>
</body>
</html>
+34 -27
View File
@@ -3,7 +3,7 @@
<head>
<link rel="icon" type="image/svg+xml" href="/img/favicon.svg">
<meta charset="UTF-8" />
<meta charset="UTF-8"/>
<title>Elsewhere Settings</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<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"
rel="stylesheet">
<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">
<link rel="stylesheet" href="/css/shell.css?v=20260909-account-controls">
</head>
<body class="site-page settings-page" data-theme="fakegpt" data-theme-mode="light">
<!-- site-header -->
<main id="main" class="settings-shell page-content">
<!-- site-header -->
<main id="main" class="settings-shell page-content">
<header class="market-header market-header-simple">
<div>
<p class="eyebrow">Elsewhere settings</p>
@@ -31,12 +31,15 @@
<section class="settings-layout">
<aside class="settings-nav" aria-label="Settings sections" role="tablist">
<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"
data-settings-view="design">Appearance</button>
data-settings-view="design">Appearance
</button>
<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>
<div class="settings-content">
@@ -51,15 +54,15 @@
<form id="usernameForm" class="settings-form-stack">
<label class="field">
<span>Current username</span>
<input id="currentUsername" readonly />
<input id="currentUsername" readonly/>
</label>
<label class="field">
<span>New username</span>
<input id="newUsername" autocomplete="username" />
<input id="newUsername" autocomplete="username"/>
</label>
<label class="field">
<span>Current password</span>
<input id="usernamePassword" type="password" autocomplete="current-password" />
<input id="usernamePassword" type="password" autocomplete="current-password"/>
</label>
<button id="saveUsername" type="submit">Save username</button>
</form>
@@ -75,15 +78,15 @@
<form id="passwordForm" class="settings-form-stack">
<label class="field">
<span>Current password</span>
<input id="currentPassword" type="password" autocomplete="current-password" />
<input id="currentPassword" type="password" autocomplete="current-password"/>
</label>
<label class="field">
<span>New password</span>
<input id="newPassword" type="password" autocomplete="new-password" />
<input id="newPassword" type="password" autocomplete="new-password"/>
</label>
<label class="field">
<span>Repeat new password</span>
<input id="repeatPassword" type="password" autocomplete="new-password" />
<input id="repeatPassword" type="password" autocomplete="new-password"/>
</label>
<button id="savePassword" type="submit">Save password</button>
</form>
@@ -101,7 +104,8 @@
<div class="persona-actions">
<button id="exportAccount" type="button" class="secondary-action">Export account</button>
<button id="logoutAllSessions" type="button" class="secondary-action">Logout all
sessions</button>
sessions
</button>
</div>
</div>
@@ -116,7 +120,8 @@
</div>
<div class="persona-actions">
<button id="deleteAccount" type="button" class="secondary-action danger">Delete
account</button>
account
</button>
</div>
</div>
</section>
@@ -128,7 +133,8 @@
<div>
<p class="eyebrow">Curated assistants</p>
<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>
<a class="secondary-action" href="/app/personas">Browse Personalities</a>
</div>
@@ -221,10 +227,11 @@
</section>
</div>
</section>
</main>
</main>
<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 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-header">
<div class="persona-modal-heading">
<p class="eyebrow">Persona editor</p>
@@ -247,11 +254,11 @@
</label>
<label class="field">
<span>Name</span>
<input id="settingsPersonaName" placeholder="Persona name" required />
<input id="settingsPersonaName" placeholder="Persona name" required/>
</label>
<label class="field">
<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 class="field field-wide">
<span>Appearance</span>
@@ -284,9 +291,9 @@
</div>
</form>
</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"
aria-describedby="settingsPopupDescription">
<div class="modal-header">
@@ -300,7 +307,7 @@
<p id="settingsPopupDescription" class="subtitle">Please confirm this action.</p>
<label id="settingsPopupField" class="field">
<span id="settingsPopupInputLabel">Value</span>
<input id="settingsPopupInput" type="text" autocomplete="off" />
<input id="settingsPopupInput" type="text" autocomplete="off"/>
</label>
</div>
<div class="popup-actions">
@@ -308,9 +315,9 @@
<button id="settingsPopupConfirm" type="button">Confirm</button>
</div>
</div>
</div>
<script type="module" src="/js/settings.js?v=20260930-account-fixes"></script>
<script type="module" src="/js/workspaceNavigation.js?v=20260909-account-controls"></script>
</div>
<script type="module" src="/js/settings.js?v=20260930-account-fixes"></script>
<script type="module" src="/js/workspaceNavigation.js?v=20260909-account-controls"></script>
</body>
</html>
+15 -10
View File
@@ -17,14 +17,16 @@
</head>
<body class="site-page legal-page">
<!-- site-header -->
<main id="main" class="legal-shell">
<!-- site-header -->
<main id="main" class="legal-shell">
<header class="legal-hero">
<p class="eyebrow">The ground rules</p>
<h1>Terms of Service</h1>
<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>
<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>
<div class="legal-layout">
@@ -99,9 +101,11 @@
<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>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
limits.</li>
limits.
</li>
</ul>
<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>
@@ -139,7 +143,8 @@
<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
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 id="law">
@@ -161,14 +166,14 @@
</section>
</article>
</div>
</main>
</main>
<footer class="legal-footer">
<footer class="legal-footer">
<span>Elsewhere</span>
<a href="/privacy.html">Privacy Policy</a>
<a href="/">Back to Elsewhere</a>
</footer>
<script type="module" src="/js/workspaceNavigation.js?v=20260923-legal"></script>
</footer>
<script type="module" src="/js/workspaceNavigation.js?v=20260923-legal"></script>
</body>
</html>
+14 -7
View File
@@ -1,9 +1,9 @@
const entries = [
{
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.",
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.
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",
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.",
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.
@@ -27,7 +27,7 @@ Track continuity across the conversation, including names, relationships, chrono
},
{
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.",
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.
@@ -53,7 +53,7 @@ When sources conflict, compare their evidence, methods, definitions, incentives,
},
{
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.",
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.
@@ -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)]));
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() {
return entries.map(entry => ({
id: entry.id,
@@ -95,4 +99,7 @@ export function listPersonalities() {
...(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;
}