personal memory agent
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

convey: lode 1 canon sweep — lowercase app labels, surveillance vocab, brand re-cap

Apply the system-anatomy + voice-terminology brand canon to four founder-flagged
dimensions of the convey UI:

- D1: lowercase all 19 app-registry labels; observer label pluralizes to
"observers" (canon plural); drop the .title() fallback in apps/__init__.py
so a missing label surfaces the verbatim app_name instead of re-Title-Casing.
- D2: replace surveillance vocabulary (Capture/Capturing/Local capture/
Captured Hours/tracking/tracked) on owner-facing strings across health,
graph, stats, entities, home, transcripts, and settings. Code-side
identifiers (CSS classes, JSON keys, variable names) untouched.
- D3: lowercase brand-name occurrences of Solstone in owner-facing copy
(import detail, speakers).
- D5: one users → owners swap in settings copy.

Line-level edits only; no component or layout changes. Adjacent surveillance-
vocab strings discovered during recon (apps/health/workspace.html status
lines, transcripts empty-state line 3172, home capture_status passthrough)
are deferred to lode 2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

+55 -55
+1 -1
apps/__init__.py
··· 161 161 162 162 # Get icon and label (with defaults) 163 163 icon = metadata.get("icon", "📦") 164 - label = metadata.get("label", app_name.replace("_", " ").title()) 164 + label = metadata.get("label", app_name.replace("_", " ")) 165 165 166 166 # Parse facets config 167 167 facets_config = metadata.get("facets", {})
+1 -1
apps/activities/app.json
··· 1 1 { 2 2 "icon": "📅", 3 - "label": "Activities", 3 + "label": "activities", 4 4 "date_nav": true, 5 5 "allow_future_dates": true 6 6 }
+1 -1
apps/chat/app.json
··· 1 1 { 2 2 "icon": "💬", 3 - "label": "Chat", 3 + "label": "chat", 4 4 "date_nav": true, 5 5 "allow_future_dates": false, 6 6 "app_bar": false
+1 -1
apps/entities/app.json
··· 1 1 { 2 2 "icon": "📇", 3 - "label": "Entities" 3 + "label": "entities" 4 4 }
+1 -1
apps/entities/workspace.html
··· 1394 1394 <input type="text" id="entity-card-search" class="entity-search-input" placeholder="search entities..." aria-label="Search entity cards"> 1395 1395 <span id="facet-card-search-count" class="entity-card-search-count" style="display: none;"></span> 1396 1396 </h3> 1397 - <p class="entities-subtitle" id="facet-entities-subtitle">people, places, and things you're tracking</p> 1397 + <p class="entities-subtitle" id="facet-entities-subtitle">people, places, and things you're tending</p> 1398 1398 <div class="entity-cards-container"> 1399 1399 <div id="entity-cards-content"></div> 1400 1400 <div id="no-facet-entities" class="no-entities" style="display: none;">
+1 -1
apps/graph/app.json
··· 1 1 { 2 2 "icon": "\ud83d\udd78\ufe0f", 3 - "label": "Graph" 3 + "label": "graph" 4 4 }
+1 -1
apps/graph/workspace.html
··· 36 36 <div class="surface-state surface-state--empty"> 37 37 <div class="surface-state-icon" aria-hidden="true">🕸️</div> 38 38 <h2 class="surface-state-heading">Your knowledge graph builds itself from daily use</h2> 39 - <p class="surface-state-desc">As solstone captures your meetings, conversations, and work, entities and relationships appear here automatically.</p> 39 + <p class="surface-state-desc">as solstone takes in your meetings, conversations, and work, entities and relationships appear here automatically.</p> 40 40 <div class="surface-state-action"><a href="/app/home" class="graph-empty-action">get started with solstone →</a></div> 41 41 </div> 42 42 </div>
+1 -1
apps/health/app.json
··· 1 1 { 2 2 "icon": "🩺", 3 - "label": "Health", 3 + "label": "health", 4 4 "facets": {"disabled": true} 5 5 }
+9 -9
apps/health/workspace.html
··· 361 361 white-space: nowrap; 362 362 } 363 363 364 - /* Capture hosts card */ 364 + /* Observations card */ 365 365 .observers-card { 366 366 border-left: 4px solid #22c55e; 367 367 } ··· 1126 1126 <div class="dashboard-card observe-card"> 1127 1127 <div class="card-header"> 1128 1128 <div class="card-title"> 1129 - Local capture 1129 + observations 1130 1130 </div> 1131 1131 <div class="health-badge idle" id="observeModeBadge"> 1132 1132 <span id="observeModeLabel">Waiting...</span> ··· 1134 1134 </div> 1135 1135 <div id="observeEmpty"> 1136 1136 <div class="surface-state surface-state--empty"> 1137 - <h2 class="surface-state-heading">No local capture active</h2> 1137 + <h2 class="surface-state-heading">no observations active</h2> 1138 1138 <div class="surface-state-action"><a href="/app/observer/">Manage observers →</a></div> 1139 1139 </div> 1140 1140 </div> ··· 1150 1150 <div class="observe-section-detail" id="tmuxDetail"></div> 1151 1151 </div> 1152 1152 <div class="observe-section"> 1153 - <div class="observe-section-title">Audio Capture</div> 1153 + <div class="observe-section-title">audio</div> 1154 1154 <div class="observe-section-value" id="audioStatus">Waiting...</div> 1155 1155 <div class="observe-section-detail" id="audioDetail"></div> 1156 1156 </div> ··· 1172 1172 </div> 1173 1173 </div> 1174 1174 1175 - <!-- Capture hosts card --> 1175 + <!-- Observations card --> 1176 1176 <div class="dashboard-card observers-card hidden" id="observersCard"> 1177 1177 <div class="card-header"> 1178 1178 <div class="card-title"> 1179 - Capture hosts 1179 + observations 1180 1180 </div> 1181 1181 </div> 1182 1182 <div class="observers-grid" id="observersGrid"></div> ··· 2044 2044 function updateObserveMode() { 2045 2045 if (state.observers.size === 0) { 2046 2046 elements.observeModeBadge.className = 'health-badge idle'; 2047 - elements.observeModeLabel.textContent = 'No capture'; 2047 + elements.observeModeLabel.textContent = 'idle'; 2048 2048 return; 2049 2049 } 2050 2050 ··· 2054 2054 2055 2055 if (mode === 'screencast') { 2056 2056 badge.className = 'health-badge recording'; 2057 - label.textContent = 'Recording'; 2057 + label.textContent = 'observing'; 2058 2058 } else if (mode === 'tmux') { 2059 2059 badge.className = 'health-badge tmux'; 2060 2060 label.textContent = 'Terminal Sessions'; ··· 2115 2115 const elapsed = tmux.tmux.window_elapsed_seconds || 0; 2116 2116 const mins = Math.max(1, Math.round(elapsed / 60)); 2117 2117 return { 2118 - status: `Capturing (${captures} snapshots, ~${mins} min)`, 2118 + status: `observing (${captures} snapshots, ~${mins} min)`, 2119 2119 detail: sessions.length > 0 ? sessions.join(', ') : '', 2120 2120 }; 2121 2121 },
+1 -1
apps/home/app.json
··· 1 1 { 2 2 "icon": "🏠", 3 - "label": "Home", 3 + "label": "home", 4 4 "facets": {"disabled": true} 5 5 }
+2 -2
apps/home/workspace.html
··· 1328 1328 {% if not entities and not show_welcome %} 1329 1329 <div class="pulse-empty-state"> 1330 1330 <h2 class="pulse-section-header">network pulse</h2> 1331 - <div class="pulse-empty-message">no one has appeared in today's record yet.</div> 1331 + <div class="pulse-empty-message">no one has appeared in today's journal yet.</div> 1332 1332 </div> 1333 1333 {% endif %} 1334 1334 ··· 1610 1610 html += '</div>'; 1611 1611 } 1612 1612 if (data.last_observe_relative) { 1613 - html += '<div class="pulse-vitals-sep"></div><div class="pulse-vitals-item">Last observation ' + esc(data.last_observe_relative) + '</div>'; 1613 + html += '<div class="pulse-vitals-sep"></div><div class="pulse-vitals-item">last observation ' + esc(data.last_observe_relative) + '</div>'; 1614 1614 } 1615 1615 if (data.attention) { 1616 1616 html += '<div class="pulse-vitals-sep"></div><div class="pulse-vitals-item pulse-vitals-attention">' + esc(data.attention.placeholder_text) + '</div>';
+1 -1
apps/import/_detail.html
··· 561 561 overviewHtml += ` 562 562 <div class="merge-collision-callout"> 563 563 <h3>Owner identity differs between journals</h3> 564 - <p>This journal belongs to ${escapeHtml(principalCollision.target_name || '')}, and the imported archive marks ${escapeHtml(principalCollision.source_name || '')} as the owner. Solstone kept this journal&#39;s owner and brought the other person in as a regular entity.</p> 564 + <p>This journal belongs to ${escapeHtml(principalCollision.target_name || '')}, and the imported archive marks ${escapeHtml(principalCollision.source_name || '')} as the owner. solstone kept this journal&#39;s owner and brought the other person in as a regular entity.</p> 565 565 </div> 566 566 `; 567 567 }
+1 -1
apps/import/app.json
··· 1 1 { 2 2 "icon": "📥", 3 - "label": "Import" 3 + "label": "import" 4 4 }
+1 -1
apps/link/app.json
··· 1 1 { 2 2 "icon": "🔗", 3 - "label": "Link", 3 + "label": "link", 4 4 "facets": {"disabled": true} 5 5 }
+1 -1
apps/observer/app.json
··· 1 1 { 2 2 "icon": "📡", 3 - "label": "Observer", 3 + "label": "observers", 4 4 "facets": {"disabled": true} 5 5 }
+1 -1
apps/reflections/app.json
··· 1 1 { 2 2 "icon": "🪞", 3 - "label": "Reflections", 3 + "label": "reflections", 4 4 "date_nav": true, 5 5 "allow_future_dates": false, 6 6 "facets": {
+1 -1
apps/search/app.json
··· 1 1 { 2 2 "icon": "🔍", 3 - "label": "Search" 3 + "label": "search" 4 4 }
+1 -1
apps/settings/app.json
··· 1 1 { 2 2 "icon": "⚙️", 3 - "label": "Settings" 3 + "label": "settings" 4 4 }
+3 -3
apps/settings/workspace.html
··· 2172 2172 </div> 2173 2173 </div> 2174 2174 <h3 class="settings-subsection-title">google backend</h3> 2175 - <p class="settings-subsection-desc">How solstone connects to Google AI. Auto-detect works for most users.</p> 2175 + <p class="settings-subsection-desc">how solstone connects to Google AI. auto-detect works for most owners.</p> 2176 2176 <div class="provider-row"> 2177 2177 <div class="settings-field"> 2178 2178 <label for="field-google-backend">backend</label> ··· 2747 2747 </label> 2748 2748 <label for="field-support-enabled" class="toggle-label" style="cursor: pointer;">support agent</label> 2749 2749 </div> 2750 - <small>When enabled, your sol can file and track support tickets on your behalf. You always review and approve before anything is sent.</small> 2750 + <small>when enabled, your sol can file and follow up on support tickets on your behalf. you always review and approve before anything is sent.</small> 2751 2751 </div> 2752 2752 2753 2753 <div class="settings-field"> ··· 2874 2874 <!-- Facet Activities Section --> 2875 2875 <section class="settings-section" id="section-facet-activities" data-requires-facet role="tabpanel" aria-labelledby="tab-facet-activities"> 2876 2876 <h2>activities</h2> 2877 - <p class="settings-section-desc">Configure which activities are tracked for this facet.</p> 2877 + <p class="settings-section-desc">configure which activities are observed for this facet.</p> 2878 2878 2879 2879 <!-- Attached Activities --> 2880 2880 <div class="activities-section">
+1 -1
apps/sol/app.json
··· 1 1 { 2 2 "icon": "🦾", 3 - "label": "Sol", 3 + "label": "sol", 4 4 "date_nav": true 5 5 }
+1 -1
apps/speakers/app.json
··· 1 1 { 2 2 "icon": "🎙️", 3 - "label": "Speakers", 3 + "label": "speakers", 4 4 "date_nav": true, 5 5 "facets": { 6 6 "disabled": true
+3 -3
apps/speakers/workspace.html
··· 1214 1214 let detailExpanded = false; 1215 1215 let ownerDetectionInFlight = false; 1216 1216 const OWNER_HELP_TOAST_KEY = 'speakers-owner-help-toast'; 1217 - const OWNER_HELP_TOAST_MESSAGE = 'Click on segments and tag your own statements with the assign button. Solstone needs ~30 longer ones to lock in your voice.'; 1217 + const OWNER_HELP_TOAST_MESSAGE = 'click on segments and tag your own statements with the assign button. solstone needs ~30 longer ones to lock in your voice.'; 1218 1218 1219 1219 checkOwnerStatus(); 1220 1220 loadSegments(); ··· 1468 1468 ownerBanner.style.display = 'block'; 1469 1469 ownerBanner.innerHTML = ` 1470 1470 <div class="spk-owner-panel"> 1471 - <div class="spk-owner-title">Solstone is still learning your voice</div> 1471 + <div class="spk-owner-title">solstone is still learning your voice</div> 1472 1472 <div class="spk-owner-copy"> 1473 - We're tagging audio segments to recognize you. Help Solstone learn faster by tagging your own statements. 1473 + we're tagging audio segments to recognize you. help solstone learn faster by tagging your own statements. 1474 1474 </div> 1475 1475 <div class="spk-owner-actions"> 1476 1476 <button class="spk-owner-btn spk-owner-btn-confirm" id="spkOwnerHelp">
+1 -1
apps/stats/app.json
··· 1 1 { 2 2 "icon": "📊", 3 - "label": "Stats", 3 + "label": "stats", 4 4 "facets": {"disabled": true} 5 5 }
+7 -7
apps/stats/static/dashboard.js
··· 6 6 'use strict'; 7 7 8 8 const EXPECTED_SCHEMA_VERSION = 4; 9 - const DISPLAY_LABELS = { transcript: 'Audio', percept: 'Screen' }; 9 + const DISPLAY_LABELS = { transcript: 'audio', percept: 'screen' }; 10 10 11 11 // DOM element factory 12 12 function el(tag, attrs = {}, children = []) { ··· 266 266 container.appendChild( 267 267 el('div', {className: 'empty-chart'}, [ 268 268 el('div', {style: 'font-size: 2em;'}, ['🎙️']), 269 - el('div', {style: 'font-weight: 600; font-size: 1.1em;'}, ['No capture data']), 270 - el('div', {style: 'color: #999;'}, ['No audio or screen capture hours recorded']) 269 + el('div', {style: 'font-weight: 600; font-size: 1.1em;'}, ['no observations yet']), 270 + el('div', {style: 'color: #999;'}, ['no audio or screen observations yet']) 271 271 ]) 272 272 ); 273 273 return; ··· 276 276 // Calculate max total for scaling 277 277 const maxTotal = Math.max(...data.map(d => d.audio + d.screen)) || 1; 278 278 279 - const chart = el('div', {className: 'bar-chart', role: 'img', 'aria-label': 'Captured hours bar chart showing audio and screen time per day'}); 279 + const chart = el('div', {className: 'bar-chart', role: 'img', 'aria-label': 'observation hours bar chart showing audio and screen time per day'}); 280 280 281 281 data.forEach((d, i) => { 282 282 const total = d.audio + d.screen; ··· 334 334 const legend = el('div', {className: 'token-legend'}, [ 335 335 el('div', {className: 'legend-item'}, [ 336 336 el('div', {className: 'legend-color', style: {background: '#2171b5'}, 'aria-hidden': 'true'}), 337 - 'Audio' 337 + 'audio' 338 338 ]), 339 339 el('div', {className: 'legend-item'}, [ 340 340 el('div', { ··· 345 345 }, 346 346 'aria-hidden': 'true' 347 347 }), 348 - 'Screen' 348 + 'screen' 349 349 ]) 350 350 ]); 351 351 container.appendChild(legend); ··· 356 356 const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; 357 357 const maxVal = Math.max(...data.flat()) || 1; 358 358 359 - const heatmap = el('div', {className: 'heatmap', role: 'grid', 'aria-label': 'Activity heatmap showing captures by day of week and hour'}); 359 + const heatmap = el('div', {className: 'heatmap', role: 'grid', 'aria-label': 'activity heatmap showing observations by day of week and hour'}); 360 360 361 361 // Empty top-left corner 362 362 heatmap.appendChild(el('div'));
+6 -6
apps/stats/workspace.html
··· 561 561 </style> 562 562 563 563 <div class="dashboard" data-stats-url="{{ url_for('app:stats.stats_data') }}"> 564 - <h1>Journal Dashboard</h1> 564 + <h1>journal dashboard</h1> 565 565 566 566 <p id="statsFreshness" class="stats-freshness"></p> 567 567 ··· 579 579 580 580 <div class="chart-section"> 581 581 <div class="chart-header"> 582 - <h2>Token Activity (Last 30 Days)</h2> 582 + <h2>token activity (last 30 days)</h2> 583 583 <label for="modelSelector" class="sr-only">Filter by model</label> 584 584 <select id="modelSelector" class="model-select"> 585 585 <option value="">Select Model...</option> ··· 589 589 </div> 590 590 591 591 <div class="chart-section"> 592 - <h2>Captured Hours per Day</h2> 592 + <h2>observation hours per day</h2> 593 593 <div class="chart" id="audioChart"></div> 594 594 </div> 595 595 596 596 <div class="chart-section" id="heatmapSection"> 597 - <h2>Activity Heatmap</h2> 597 + <h2>activity heatmap</h2> 598 598 <div id="heatmap"></div> 599 599 </div> 600 600 601 601 <div class="chart-section"> 602 - <h2>Facets (Last 30 Days)</h2> 602 + <h2>facets (last 30 days)</h2> 603 603 <div class="chart" id="facetsChart"></div> 604 604 </div> 605 605 606 606 <div class="chart-section"> 607 - <h2>Activities (Last 30 Days)</h2> 607 + <h2>activities (last 30 days)</h2> 608 608 <div class="chart" id="activitiesChart"></div> 609 609 </div> 610 610 </div>
+1 -1
apps/support/app.json
··· 1 1 { 2 2 "icon": "🛟", 3 - "label": "Support", 3 + "label": "support", 4 4 "facets": false 5 5 }
+1 -1
apps/todos/app.json
··· 1 1 { 2 2 "icon": "✅", 3 - "label": "Todos", 3 + "label": "todos", 4 4 "date_nav": true, 5 5 "allow_future_dates": true 6 6 }
+1 -1
apps/tokens/app.json
··· 1 1 { 2 2 "icon": "💰", 3 - "label": "Tokens", 3 + "label": "tokens", 4 4 "date_nav": true, 5 5 "facets": {"disabled": true} 6 6 }
+1 -1
apps/transcripts/app.json
··· 1 1 { 2 2 "icon": "📜", 3 - "label": "Transcripts", 3 + "label": "transcripts", 4 4 "date_nav": true, 5 5 "facets": {"disabled": true} 6 6 }
+2 -2
apps/transcripts/workspace.html
··· 2551 2551 const tabEmptyMap = { 2552 2552 transcript: { icon: emptyIcons.transcript, heading: 'no transcript entries', desc: 'this segment has no transcript content' }, 2553 2553 audio: { icon: emptyIcons.audio, heading: 'no audio entries', desc: 'this segment has no audio content' }, 2554 - screen: { icon: emptyIcons.screen, heading: 'no screen entries', desc: 'this segment has no screen captures' } 2554 + screen: { icon: emptyIcons.screen, heading: 'no screen entries', desc: 'this segment has no screen observations' } 2555 2555 }; 2556 2556 const emptyInfo = tabEmptyMap[tabType] || tabEmptyMap.transcript; 2557 2557 targetEl.innerHTML = window.SurfaceState.empty(emptyInfo); ··· 3168 3168 if (allSegments.length === 0) { 3169 3169 panel.innerHTML = window.SurfaceState.empty({ 3170 3170 icon: emptyIcons.nothing, 3171 - heading: 'nothing captured', 3171 + heading: 'nothing here', 3172 3172 desc: 'no recordings were found for this day' 3173 3173 }); 3174 3174 }