personal memory agent
0
fork

Configure Feed

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

init: 3-state retention UX in onboarding section 5

+54 -27
+54 -27
convey/templates/init.html
··· 89 89 font-size: 0.85rem; box-sizing: border-box; 90 90 } 91 91 .retention-days-row input[type="number"]:focus { outline: none; border-color: #E8923A; } 92 + .retention-days-row.is-disabled { opacity: 0.5; cursor: not-allowed; } 92 93 </style> 93 94 <script src="{{ url_for('root.static', filename='error-handler.js') }}"></script> 94 95 <script src="{{ url_for('root.static', filename='api.js') }}"></script> ··· 174 175 </section> 175 176 176 177 <section class="init-section disabled" id="section-observed-media"> 177 - <h3>5. your observed media</h3> 178 - <p class="section-hint">choose how long to keep observed audio and screen media. you can change this anytime in settings.</p> 178 + <h3>5. your observed media (raw audio and screencast files)</h3> 179 + <p class="section-hint">by default, solstone retains your observed media so your journal can re-derive insights later. we recommend leaving this on. you can also expire originals after a number of days, or delete them as soon as they're processed. <a href="https://solpbc.org/privacy#recording-laws" target="_blank" rel="noopener">recording-consent laws</a> may apply in your jurisdiction.</p> 179 180 <div class="field-group"> 180 - <div style="display: flex; gap: 0.5rem; flex-wrap: wrap;"> 181 - <button type="button" class="settings-mode-btn active" data-mode="days" onclick="setRetentionMode('days')">keep N days</button> 182 - <button type="button" class="settings-mode-btn" data-mode="keep" onclick="setRetentionMode('keep')">keep forever</button> 183 - <button type="button" class="settings-mode-btn" data-mode="processed" onclick="setRetentionMode('processed')">delete after processing</button> 184 - </div> 185 181 <div id="retention-days-field" class="retention-days-row"> 186 - <button type="button" class="settings-preset-btn active" data-days="7" onclick="setRetentionDays(7)">7</button> 187 - <button type="button" class="settings-preset-btn" data-days="30" onclick="setRetentionDays(30)">30</button> 188 - <button type="button" class="settings-preset-btn" data-days="90" onclick="setRetentionDays(90)">90</button> 189 - <input type="number" id="retention-days-input" min="1" max="365" value="7" onchange="setRetentionDaysCustom(this.value)"> 182 + <button type="button" class="settings-preset-btn" data-days="7">7</button> 183 + <button type="button" class="settings-preset-btn" data-days="30">30</button> 184 + <button type="button" class="settings-preset-btn" data-days="90">90</button> 185 + <input type="number" id="retention-days-input" min="1" max="365" value=""> 190 186 <span style="font-size: 0.85rem; color: #666;">days</span> 191 187 </div> 188 + <label for="retention-dont-retain" class="section-hint" style="display: flex; align-items: center; gap: 0.5rem; margin-top: 0.75rem;"> 189 + <input type="checkbox" id="retention-dont-retain"> 190 + <span>don't retain observed media (delete as soon as it's processed)</span> 191 + </label> 192 + <p id="retention-status" class="section-hint" style="margin-top: 0.75rem;">currently: always retain — no expiration</p> 192 193 </div> 193 194 </section> 194 195 ··· 330 331 } 331 332 } 332 333 333 - function setRetentionMode(mode) { 334 - document.querySelectorAll('#section-observed-media .settings-mode-btn').forEach(btn => { 335 - btn.classList.toggle('active', btn.dataset.mode === mode); 336 - }); 337 - document.getElementById('retention-days-field').style.display = mode === 'days' ? 'flex' : 'none'; 334 + function deriveRetention(daysValue, dontRetain) { 335 + if (dontRetain) return { mode: 'processed', days: null, statusText: 'currently: delete observed media after processing' }; 336 + const n = parseInt(daysValue, 10); 337 + if (Number.isFinite(n) && n >= 1) return { mode: 'days', days: n, statusText: `currently: keep ${n} days` }; 338 + return { mode: 'keep', days: null, statusText: 'currently: always retain — no expiration' }; 338 339 } 339 340 340 - function setRetentionDays(days) { 341 - document.querySelectorAll('#section-observed-media .settings-preset-btn').forEach(btn => { 342 - btn.classList.toggle('active', parseInt(btn.dataset.days) === days); 341 + function updateRetentionUI() { 342 + const daysField = document.getElementById('retention-days-field'); 343 + const daysInput = document.getElementById('retention-days-input'); 344 + const dontRetainCheckbox = document.getElementById('retention-dont-retain'); 345 + const presetButtons = document.querySelectorAll('#section-observed-media .settings-preset-btn'); 346 + const statusEl = document.getElementById('retention-status'); 347 + const disabled = dontRetainCheckbox.checked; 348 + 349 + if (disabled) { 350 + daysInput.value = ''; 351 + } 352 + 353 + const { statusText } = deriveRetention(daysInput.value, disabled); 354 + statusEl.textContent = statusText; 355 + daysField.classList.toggle('is-disabled', disabled); 356 + daysInput.disabled = disabled; 357 + daysInput.setAttribute('aria-disabled', disabled ? 'true' : 'false'); 358 + 359 + const activeDays = disabled ? null : parseInt(daysInput.value, 10); 360 + presetButtons.forEach(btn => { 361 + const isActive = Number.isFinite(activeDays) && parseInt(btn.dataset.days, 10) === activeDays; 362 + btn.classList.toggle('active', isActive); 363 + btn.disabled = disabled; 364 + btn.setAttribute('aria-disabled', disabled ? 'true' : 'false'); 343 365 }); 344 - document.getElementById('retention-days-input').value = days; 345 366 } 346 367 347 - function setRetentionDaysCustom(value) { 348 - const days = parseInt(value) || 7; 349 - document.querySelectorAll('#section-observed-media .settings-preset-btn').forEach(btn => { 350 - btn.classList.toggle('active', parseInt(btn.dataset.days) === days); 368 + document.querySelectorAll('#section-observed-media .settings-preset-btn').forEach(btn => { 369 + btn.addEventListener('click', () => { 370 + document.getElementById('retention-days-input').value = btn.dataset.days; 371 + updateRetentionUI(); 351 372 }); 352 - } 373 + }); 374 + document.getElementById('retention-days-input').addEventListener('input', updateRetentionUI); 375 + document.getElementById('retention-dont-retain').addEventListener('change', updateRetentionUI); 376 + updateRetentionUI(); 353 377 354 378 async function finalize() { 355 379 const passwordEl = document.getElementById('password'); 356 380 const errorEl = document.getElementById('finalize-error'); 381 + const daysInput = document.getElementById('retention-days-input'); 382 + const dontRetainCheckbox = document.getElementById('retention-dont-retain'); 357 383 try { 384 + const { mode, days } = deriveRetention(daysInput.value, dontRetainCheckbox.checked); 358 385 const data = await window.apiJson('/init/finalize', { 359 386 method: 'POST', 360 387 headers: {'Content-Type': 'application/json'}, ··· 364 391 preferred: document.getElementById('preferred').value.trim(), 365 392 timezone: detectedTimezone, 366 393 gemini_key: document.getElementById('gemini-key').value.trim(), 367 - retention_mode: (document.querySelector('#section-observed-media .settings-mode-btn.active') || {}).dataset?.mode || 'days', 368 - retention_days: parseInt(document.getElementById('retention-days-input').value) || 7 394 + retention_mode: mode, 395 + retention_days: days 369 396 }) 370 397 }); 371 398 if (errorEl) {