Repository navigation
feat(ui): persist sidebar tab + polish plugins prompt templates + docs refactor #102
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
fa1522e
ab21034
2028b42
6881178
ddd7f4c
6c66f1a
333feaa
81c9525
a59ac1f
72ddb63
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -4,6 +4,46 @@ export function createNavigationMethods(options = {}) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| switchMainTabHelper, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| loadMoreSessionMessagesHelper | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } = options; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const NAV_STATE_STORAGE_KEY = 'codexmateNavState.v1'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const MAIN_TAB_SET = new Set([ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'config', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'sessions', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'usage', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'orchestration', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'market', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'plugins', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'docs', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'settings' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const readNavState = () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (typeof localStorage === 'undefined') return null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let raw = ''; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raw = localStorage.getItem(NAV_STATE_STORAGE_KEY) || ''; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (_) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raw = ''; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!raw) return null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const parsed = JSON.parse(raw); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return parsed && typeof parsed === 'object' ? parsed : null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (_) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const persistNavState = (vm) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!vm || vm.__navStateRestoring) return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (typeof localStorage === 'undefined') return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const mainTab = typeof vm.mainTab === 'string' ? vm.mainTab.trim().toLowerCase() : ''; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const configMode = typeof vm.configMode === 'string' ? vm.configMode.trim().toLowerCase() : ''; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const snapshot = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mainTab: MAIN_TAB_SET.has(mainTab) ? mainTab : 'docs', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| configMode: configModeSet && configModeSet.has(configMode) ? configMode : 'codex' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| localStorage.setItem(NAV_STATE_STORAGE_KEY, JSON.stringify(snapshot)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (_) {} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+18
to
+45
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: Yes, accessing window.localStorage can throw a SecurityError in browsers under specific conditions related to blocked storage or opaque origins. Key scenarios from authoritative sources: - Opaque origins (e.g., file://, data:, about:blank without proper URL, or certain sandboxed contexts): Browsers throw SecurityError because the origin lacks a valid scheme/host/port tuple. This is common in local file access, Jest/jsdom tests without configured URL, and some iframe setups. - Blocked storage due to privacy settings: - Chrome/Chromium: "Block third-party cookies and site data" setting denies access, especially in iframes from third-party contexts, throwing "Access is denied for this document." - Safari: "Block All Cookies" or restrictive "Prevent Cross-Site Tracking" settings throw SecurityError ("The operation is insecure"), even for same-origin access. Official MDN documentation for window.localStorage explicitly states it throws SecurityError if: - Origin is not a valid scheme/host/port tuple (opaque origins). - Request violates a policy decision, e.g., user-configured blocking of persistent data (browsers often extend cookie blocking to storage). Developers should wrap access in try-catch and provide fallbacks, as recommended in sources. To safely check availability: try { localStorage.setItem('test', 'test'); localStorage.removeItem('test'); // Available } catch (e) { // Not available, use fallback } Citations:
Wrap Accessing Proposed fix+ const getNavStorage = () => {
+ try {
+ return globalThis.localStorage || null;
+ } catch (_) {
+ return null;
+ }
+ };
const readNavState = () => {
- if (typeof localStorage === 'undefined') return null;
+ const storage = getNavStorage();
+ if (!storage) return null;
let raw = '';
try {
- raw = localStorage.getItem(NAV_STATE_STORAGE_KEY) || '';
+ raw = storage.getItem(NAV_STATE_STORAGE_KEY) || '';
} catch (_) {
raw = '';
}
@@
const persistNavState = (vm) => {
if (!vm || vm.__navStateRestoring) return;
- if (typeof localStorage === 'undefined') return;
+ const storage = getNavStorage();
+ if (!storage) return;
const mainTab = typeof vm.mainTab === 'string' ? vm.mainTab.trim().toLowerCase() : '';
const configMode = typeof vm.configMode === 'string' ? vm.configMode.trim().toLowerCase() : '';
@@
};
try {
- localStorage.setItem(NAV_STATE_STORAGE_KEY, JSON.stringify(snapshot));
+ storage.setItem(NAV_STATE_STORAGE_KEY, JSON.stringify(snapshot));
} catch (_) {}
};📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| switchConfigMode(mode) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -34,6 +74,7 @@ export function createNavigationMethods(options = {}) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.scheduleAfterFrame(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.clearMainTabSwitchIntent('config'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| persistNavState(this); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.switchMainTab('config'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -324,6 +365,7 @@ export function createNavigationMethods(options = {}) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| switchState.ticket += 1; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| switchState.pendingTarget = ''; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const result = switchMainTabHelper.call(this, targetTab); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| persistNavState(this); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.scheduleAfterFrame(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.clearMainTabSwitchIntent(normalizedTab); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -338,6 +380,7 @@ export function createNavigationMethods(options = {}) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const pendingTarget = liveState.pendingTarget || targetTab; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| liveState.pendingTarget = ''; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| switchMainTabHelper.call(this, pendingTarget); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| persistNavState(this); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.clearMainTabSwitchIntent(normalizedTab); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -34,6 +34,18 @@ export function createStartupClaudeMethods(options = {}) { | |
| startupOk = true; | ||
| this.currentProvider = statusRes.provider; | ||
| this.currentModel = statusRes.model; | ||
| try { | ||
| const installRes = await api('install-status'); | ||
| if (installRes && !installRes.error) { | ||
| const targets = Array.isArray(installRes.targets) ? installRes.targets : null; | ||
| if (targets) { | ||
| this.installStatusTargets = targets; | ||
| } | ||
| if (typeof installRes.packageManager === 'string' && typeof this.normalizeInstallPackageManager === 'function') { | ||
| this.installPackageManager = this.normalizeInstallPackageManager(installRes.packageManager); | ||
| } | ||
| } | ||
| } catch (_) {} | ||
|
Comment on lines
+37
to
+48
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don’t let install-status failures masquerade as missing CLIs. This best-effort probe is swallowed on error, but the app’s initial targets are 🤖 Prompt for AI Agents |
||
| { | ||
| const tier = typeof statusRes.serviceTier === 'string' | ||
| ? statusRes.serviceTier.trim().toLowerCase() | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,26 @@ | |
| id="panel-config-claude" | ||
| role="tabpanel" | ||
| :aria-labelledby="'tab-config-claude'"> | ||
| <template v-if="shouldShowCliInstallPlaceholder('claude')"> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Gate the placeholder on a verified install-status response.
Consider tracking a loaded/known status before showing this placeholder. 🛡️ Possible direction- <template v-if="shouldShowCliInstallPlaceholder('claude')">
+ <template v-if="installStatusLoaded && shouldShowCliInstallPlaceholder('claude')">Then set 🤖 Prompt for AI Agents |
||
| <div class="selector-section"> | ||
| <div class="empty-state"> | ||
| <div class="empty-state-title">{{ t('cli.missing.title', { name: 'Claude' }) }}</div> | ||
| <div class="empty-state-subtitle">{{ t('cli.missing.subtitle', { name: 'Claude' }) }}</div> | ||
| <div class="docs-command-row"> | ||
| <div class="docs-command-box" role="group" :aria-label="t('cli.missing.commandAria', { name: 'Claude' })"> | ||
| <code class="install-command">{{ getInstallCommand('claude', 'install') }}</code> | ||
| <button | ||
| type="button" | ||
| class="btn-mini docs-copy-btn" | ||
| :disabled="!getInstallCommand('claude', 'install')" | ||
| @click="copyInstallCommand(getInstallCommand('claude', 'install'))">{{ t('common.copy') }}</button> | ||
| </div> | ||
| </div> | ||
| <button type="button" class="btn-tool btn-tool-compact" @click="mainTab = 'docs'; setInstallCommandAction('install')">{{ t('cli.missing.openDocs') }}</button> | ||
| </div> | ||
| </div> | ||
| </template> | ||
| <template v-else> | ||
| <!-- 添加提供商按钮 --> | ||
| <button class="btn-add" @click="openClaudeConfigModal" v-if="!loading && !initError"> | ||
| <svg class="icon" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2"> | ||
|
|
@@ -135,4 +155,5 @@ | |
| </div> | ||
| </div> | ||
| </div> | ||
| </template> | ||
| </div> | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Require a known target before showing the missing-CLI placeholder.
shouldShowCliInstallPlaceholder('')or a target omitted frominstallStatusTargetscurrently returnstrue, so partial/unknown status can hide the config UI. Resolve the target first and only show the placeholder when that known target is explicitly not installed.🐛 Proposed fix
shouldShowCliInstallPlaceholder(targetId) { - return Array.isArray(this.installStatusTargets) && !this.isInstallTargetInstalled(targetId); + const target = this.getInstallStatusTarget(targetId); + return !!(target && target.installed === false); }📝 Committable suggestion
🤖 Prompt for AI Agents