personal memory agent
0
fork

Configure Feed

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

convey/settings: migrate section loaders to wave 0 primitives (wave 2)

Add section-local load-state slots for transcription, observer, sync, and storage. Route the four settings loaders through apiJson while disabling stale controls and preserving storage refresh state.

+89 -16
+89 -16
apps/settings/workspace.html
··· 2312 2312 <section class="settings-section" id="section-transcription" role="tabpanel" aria-labelledby="tab-transcription"> 2313 2313 <h2>transcription</h2> 2314 2314 <p class="settings-section-desc">Speech-to-text backend and processing settings.</p> 2315 + <div id="transcriptionLoadState" class="settings-load-state" aria-live="polite"></div> 2315 2316 2316 2317 <form class="settings-form" onsubmit="return false;"> 2317 2318 ··· 2429 2430 <section class="settings-section" id="section-observer" role="tabpanel" aria-labelledby="tab-observer"> 2430 2431 <h2>observer</h2> 2431 2432 <p class="settings-section-desc">Configure capture behavior for the observer service.</p> 2433 + <div id="observerLoadState" class="settings-load-state" aria-live="polite"></div> 2432 2434 2433 2435 <form class="settings-form" onsubmit="return false;"> 2434 2436 ··· 2561 2563 <section class="settings-section" id="section-sync" role="tabpanel" aria-labelledby="tab-sync"> 2562 2564 <h2>sync</h2> 2563 2565 <p class="settings-section-desc">Configure automatic syncing of recordings from external devices.</p> 2566 + <div id="syncLoadState" class="settings-load-state" aria-live="polite"></div> 2564 2567 2565 2568 <form class="settings-form" onsubmit="return false;"> 2566 2569 ··· 2628 2631 <section class="settings-section" id="section-storage" role="tabpanel" aria-labelledby="tab-storage"> 2629 2632 <h2>storage</h2> 2630 2633 <p class="settings-section-desc">Manage raw media retention and view storage usage.</p> 2634 + <div id="storageLoadState" class="settings-load-state" aria-live="polite"></div> 2631 2635 <div id="storageWarnings"></div> 2632 2636 2633 2637 <form class="settings-form" onsubmit="return false;"> ··· 4578 4582 // Backend metadata loaded from API 4579 4583 let transcribeBackends = []; 4580 4584 let transcribeApiKeys = {}; 4585 + let storageLoadedOnce = false; 4586 + 4587 + function setLoadState(slotId, html = '') { 4588 + const slot = document.getElementById(slotId); 4589 + if (slot) { 4590 + slot.innerHTML = html; 4591 + } 4592 + } 4593 + 4594 + function renderRefreshLoadState(message, serverMessage) { 4595 + return `<div class="surface-state-refresh-error"><strong>${escapeHtml(message)}</strong>${serverMessage ? `<p>${escapeHtml(serverMessage)}</p>` : ''}</div>`; 4596 + } 4597 + 4598 + function setObserveControlsDisabled(disabled) { 4599 + ['field-tmux-enabled', 'field-tmux-capture-interval'].forEach((id) => { 4600 + const el = document.getElementById(id); 4601 + if (el) el.disabled = disabled; 4602 + }); 4603 + } 4604 + 4605 + function setSyncControlsDisabled(disabled) { 4606 + ['field-plaud-sync-enabled', 'field-granola-sync-enabled', 'field-obsidian-sync-enabled'].forEach((id) => { 4607 + const el = document.getElementById(id); 4608 + if (el) el.disabled = disabled; 4609 + }); 4610 + } 4611 + 4612 + function setStorageControlsDisabled(disabled) { 4613 + document.querySelectorAll('#retentionModeSelector .settings-mode-btn, #retentionDaysField .settings-preset-btn').forEach((el) => { 4614 + el.disabled = disabled; 4615 + }); 4616 + const daysInput = document.getElementById('retentionDaysInput'); 4617 + if (daysInput) { 4618 + daysInput.disabled = disabled; 4619 + } 4620 + } 4581 4621 4582 4622 async function loadTranscribeBackends() { 4583 4623 try { 4584 - const response = await fetch('api/transcribe'); 4585 - const data = await response.json(); 4624 + const data = await window.apiJson('api/transcribe'); 4586 4625 transcribeBackends = data.backends || []; 4587 4626 transcribeApiKeys = data.api_keys || {}; 4627 + setLoadState('transcriptionLoadState'); 4628 + const select = document.getElementById('field-transcribe-backend'); 4629 + if (select) { 4630 + select.disabled = false; 4631 + } 4588 4632 4589 4633 // Populate the backend select 4590 - const select = document.getElementById('field-transcribe-backend'); 4591 4634 if (select) { 4592 4635 select.innerHTML = ''; 4593 4636 for (const backend of transcribeBackends) { ··· 4600 4643 4601 4644 return data; 4602 4645 } catch (err) { 4603 - console.error('Error loading transcribe backends:', err); 4646 + const select = document.getElementById('field-transcribe-backend'); 4647 + if (select) { 4648 + select.disabled = true; 4649 + } 4650 + setLoadState('transcriptionLoadState', window.SurfaceState.errorCard({ 4651 + heading: "Couldn't load transcription settings", 4652 + desc: 'Reload to try again.', 4653 + serverMessage: err?.serverMessage 4654 + })); 4655 + window.logError(err, { context: 'settings: loadTranscribeBackends failed' }); 4604 4656 return null; 4605 4657 } 4606 4658 } ··· 4725 4777 4726 4778 async function loadObserve() { 4727 4779 try { 4728 - const response = await fetch('api/observe'); 4729 - observeData = await response.json(); 4730 - if (observeData.error) throw new Error(observeData.error); 4780 + observeData = await window.apiJson('api/observe'); 4781 + setLoadState('observerLoadState'); 4782 + setObserveControlsDisabled(false); 4731 4783 populateObserve(observeData); 4732 4784 } catch (err) { 4733 - console.error('Error loading observe config:', err); 4785 + setObserveControlsDisabled(true); 4786 + setLoadState('observerLoadState', window.SurfaceState.errorCard({ 4787 + heading: "Couldn't load observer settings", 4788 + desc: 'Reload to try again.', 4789 + serverMessage: err?.serverMessage 4790 + })); 4791 + window.logError(err, { context: 'settings: loadObserve failed' }); 4734 4792 } 4735 4793 } 4736 4794 ··· 4810 4868 // ========== SYNC ========== 4811 4869 async function loadSync() { 4812 4870 try { 4813 - const response = await fetch('api/sync'); 4814 - const data = await response.json(); 4815 - if (data.error) throw new Error(data.error); 4871 + const data = await window.apiJson('api/sync'); 4872 + setLoadState('syncLoadState'); 4873 + setSyncControlsDisabled(false); 4816 4874 populateSync(data); 4817 4875 } catch (err) { 4818 - console.error('Error loading sync config:', err); 4876 + setSyncControlsDisabled(true); 4877 + setLoadState('syncLoadState', window.SurfaceState.errorCard({ 4878 + heading: "Couldn't load sync settings", 4879 + desc: 'Reload to try again.', 4880 + serverMessage: err?.serverMessage 4881 + })); 4882 + window.logError(err, { context: 'settings: loadSync failed' }); 4819 4883 } 4820 4884 } 4821 4885 ··· 5850 5914 5851 5915 async function loadStorage() { 5852 5916 try { 5853 - const response = await fetch('api/storage'); 5854 - storageData = await response.json(); 5855 - if (storageData.error) throw new Error(storageData.error); 5917 + storageData = await window.apiJson('api/storage'); 5918 + setLoadState('storageLoadState'); 5919 + setStorageControlsDisabled(false); 5920 + storageLoadedOnce = true; 5856 5921 populateStorage(storageData); 5857 5922 } catch (err) { 5858 - console.error('Error loading storage:', err); 5923 + setStorageControlsDisabled(true); 5924 + setLoadState('storageLoadState', storageLoadedOnce 5925 + ? renderRefreshLoadState("Couldn't refresh storage settings — showing last known state.", err?.serverMessage) 5926 + : window.SurfaceState.errorCard({ 5927 + heading: "Couldn't load storage settings", 5928 + desc: 'Reload to try again.', 5929 + serverMessage: err?.serverMessage 5930 + })); 5931 + window.logError(err, { context: 'settings: loadStorage failed' }); 5859 5932 } 5860 5933 } 5861 5934