diff --git a/core/flow-kernel/flow-registry.js b/core/flow-kernel/flow-registry.js
index ad3341b..cbf9792 100644
--- a/core/flow-kernel/flow-registry.js
+++ b/core/flow-kernel/flow-registry.js
@@ -57,11 +57,6 @@
'row-step-execution-range',
],
},
- 'shared-settings-actions': {
- id: 'shared-settings-actions',
- label: '\u8bbe\u7f6e\u64cd\u4f5c',
- rowIds: ['row-settings-actions'],
- },
});
function buildFlowDefinitions() {
diff --git a/flows/grok/index.js b/flows/grok/index.js
index ed2aba1..6359c9b 100644
--- a/flows/grok/index.js
+++ b/flows/grok/index.js
@@ -34,7 +34,7 @@
stepDefinitionMode: 'grok',
targetSelectorLabel: '来源',
},
- baseGroups: ['grok-runtime-status', 'shared-auto-run', 'shared-settings-actions'],
+ baseGroups: ['grok-runtime-status', 'shared-auto-run'],
targets: {
webchat2api: {
id: 'webchat2api',
diff --git a/flows/kiro/index.js b/flows/kiro/index.js
index f5a79a8..737c7e9 100644
--- a/flows/kiro/index.js
+++ b/flows/kiro/index.js
@@ -40,8 +40,7 @@
},
"baseGroups": [
"kiro-runtime-status",
- "shared-auto-run",
- "shared-settings-actions"
+ "shared-auto-run"
],
"targets": {
"kiro-rs": {
diff --git a/flows/openai/index.js b/flows/openai/index.js
index 70476f3..3a93c9d 100644
--- a/flows/openai/index.js
+++ b/flows/openai/index.js
@@ -47,8 +47,7 @@
"openai-phone",
"shared-auto-run",
"openai-oauth",
- "openai-step6",
- "shared-settings-actions"
+ "openai-step6"
],
"targets": {
"cpa": {
diff --git a/sidepanel/sidepanel.css b/sidepanel/sidepanel.css
index 3f8c28b..1d277dd 100644
--- a/sidepanel/sidepanel.css
+++ b/sidepanel/sidepanel.css
@@ -40,6 +40,10 @@
--radius-sm: 6px;
--radius-md: 8px;
+ --data-label-width: 76px;
+ --data-row-gap: 8px;
+ --data-field-height: 34px;
+ --data-inline-action-min-width: 64px;
--transition: 150ms ease;
}
@@ -893,7 +897,8 @@ header {
.data-row {
display: flex;
align-items: center;
- gap: 8px;
+ gap: var(--data-row-gap);
+ min-width: 0;
}
#settings-card .data-row.module-divider-start {
@@ -919,7 +924,24 @@ header {
display: flex;
align-items: center;
gap: 8px;
- flex: 1;
+ flex: 1 1 0;
+ min-width: 0;
+}
+
+.data-inline > .data-input,
+.data-inline > .data-select,
+.data-inline > .editable-list-picker,
+.data-inline > .input-with-icon {
+ flex: 1 1 0;
+ min-width: 0;
+}
+
+.data-inline > .auto-delay-input {
+ flex: 0 0 72px;
+}
+
+.data-inline > .data-value {
+ flex: 1 1 0;
min-width: 0;
}
@@ -1247,41 +1269,37 @@ header {
.input-with-icon {
position: relative;
- flex: 1;
+ flex: 1 1 0;
min-width: 0;
display: flex;
align-items: center;
}
.data-label {
- width: 56px;
+ flex: 0 0 var(--data-label-width);
+ width: var(--data-label-width);
font-size: 11px;
font-weight: 700;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.06em;
- flex-shrink: 0;
-}
-
-#row-codex2api-url .data-label,
-#row-codex2api-admin-key .data-label {
- width: 76px;
+ min-width: 0;
}
#row-flow-selector .data-label,
-#row-source-selector .data-label,
+#row-source-selector .data-label {
+ text-transform: none;
+ letter-spacing: 0.02em;
+}
+
+#row-codex2api-url .data-label,
+#row-codex2api-admin-key .data-label,
#row-grok-webchat2api-url .data-label,
#row-grok-webchat2api-key .data-label,
#row-grok-register-status .data-label,
#row-grok-sso-status .data-label,
#row-grok-webchat2api-upload-status .data-label,
#row-grok-sso-settings .data-label {
- width: 76px;
- text-transform: none;
- letter-spacing: 0.02em;
-}
-
-#row-codex2api-url .data-label {
font-size: 10px;
letter-spacing: 0.02em;
text-transform: none;
@@ -1364,7 +1382,10 @@ header {
}
.data-input {
- flex: 1;
+ flex: 1 1 0;
+ width: 100%;
+ min-width: 0;
+ min-height: var(--data-field-height);
padding: 7px 10px;
background: var(--bg-field);
border: 1px solid var(--border);
@@ -1374,7 +1395,6 @@ header {
font-size: 13px;
outline: none;
transition: border-color var(--transition), box-shadow var(--transition);
- min-width: 0;
}
.data-input::placeholder { color: var(--text-muted); }
.data-input:focus { border-color: var(--blue); box-shadow: 0 0 0 3px var(--blue-soft); }
@@ -1446,7 +1466,8 @@ header {
}
.choice-group {
- flex: 1;
+ flex: 1 1 0;
+ width: 100%;
min-width: 0;
display: inline-flex;
align-items: center;
@@ -1526,15 +1547,14 @@ header {
color: var(--text-secondary);
}
-#btn-fetch-email,
-#btn-save-settings {
+#btn-fetch-email {
padding-inline: 10px;
flex-shrink: 0;
}
.data-inline-btn {
- flex-shrink: 0;
- min-width: 56px;
+ flex: 0 0 auto;
+ min-width: var(--data-inline-action-min-width);
justify-content: center;
}
@@ -2072,7 +2092,10 @@ header {
}
.data-select {
- flex: 1;
+ flex: 1 1 0;
+ width: 100%;
+ min-width: 0;
+ min-height: var(--data-field-height);
padding: 7px 10px;
background: var(--bg-field);
border: 1px solid var(--border);
@@ -2083,7 +2106,6 @@ header {
outline: none;
cursor: pointer;
transition: border-color var(--transition);
- min-width: 0;
}
.data-select:focus { border-color: var(--blue); box-shadow: 0 0 0 3px var(--blue-soft); }
[data-theme="dark"] .data-select { color-scheme: dark; }
@@ -2105,7 +2127,8 @@ header {
.editable-list-picker {
position: relative;
- flex: 1;
+ flex: 1 1 0;
+ width: 100%;
min-width: 0;
}
diff --git a/sidepanel/sidepanel.html b/sidepanel/sidepanel.html
index e35dc8d..fe7cfee 100644
--- a/sidepanel/sidepanel.html
+++ b/sidepanel/sidepanel.html
@@ -405,9 +405,11 @@
GPC API
@@ -469,13 +471,15 @@
OTP 接口
@@ -770,12 +774,6 @@
等待中...
-
同步服务
diff --git a/sidepanel/sidepanel.js b/sidepanel/sidepanel.js
index b20934e..20ca9e2 100644
--- a/sidepanel/sidepanel.js
+++ b/sidepanel/sidepanel.js
@@ -60,7 +60,6 @@ const btnToggleVpsUrl = document.getElementById('btn-toggle-vps-url');
const btnToggleVpsPassword = document.getElementById('btn-toggle-vps-password');
const btnFetchEmail = document.getElementById('btn-fetch-email');
const btnTogglePassword = document.getElementById('btn-toggle-password');
-const btnSaveSettings = document.getElementById('btn-save-settings');
const btnStop = document.getElementById('btn-stop');
const btnReset = document.getElementById('btn-reset');
const btnContributionMode = document.getElementById('btn-contribution-mode');
@@ -10976,13 +10975,11 @@ function markSettingsDirty(isDirty = true) {
if (isDirty) {
settingsSaveRevision += 1;
}
- updateSaveButtonState();
+ updateSettingsSaveState();
}
-function updateSaveButtonState() {
- btnSaveSettings.disabled = settingsSaveInFlight || !settingsDirty;
+function updateSettingsSaveState() {
updateConfigMenuControls();
- btnSaveSettings.textContent = settingsSaveInFlight ? '保存中' : '保存';
}
function isEditableElementInSettingsCard(element) {
@@ -11019,7 +11016,7 @@ async function saveSettings(options = {}) {
const payload = collectSettingsPayload();
const saveRevision = settingsSaveRevision;
settingsSaveInFlight = true;
- updateSaveButtonState();
+ updateSettingsSaveState();
const shouldSkipStateApplyForFocusedEditor = (() => {
if (!silent || source !== 'autosave') {
@@ -11067,7 +11064,7 @@ async function saveSettings(options = {}) {
throw err;
} finally {
settingsSaveInFlight = false;
- updateSaveButtonState();
+ updateSettingsSaveState();
}
}
@@ -15359,14 +15356,6 @@ hotmailServiceModeButtons.forEach((button) => {
});
});
-btnSaveSettings.addEventListener('click', async () => {
- if (!settingsDirty) {
- showToast('配置已是最新', 'info', 1400);
- return;
- }
- await saveSettings({ silent: false }).catch(() => { });
-});
-
btnStop.addEventListener('click', async () => {
btnStop.disabled = true;
await chrome.runtime.sendMessage({ type: 'STOP_FLOW', source: 'sidepanel', payload: {} });
@@ -18823,8 +18812,7 @@ if (typeof initPhoneVerificationSectionExpandedState === 'function') {
initPhoneVerificationSectionExpandedState();
}
applyPhoneSmsProviderOrderSelection([], { ensureDefault: false, syncProvider: false });
-updateSaveButtonState();
-updateConfigMenuControls();
+updateSettingsSaveState();
setLocalCpaStep9Mode(DEFAULT_LOCAL_CPA_STEP9_MODE);
setMail2925Mode(DEFAULT_MAIL_2925_MODE);
setCloudflareTempEmailLookupMode(DEFAULT_CLOUDFLARE_TEMP_EMAIL_LOOKUP_MODE);
diff --git a/tests/flow-capabilities-module.test.js b/tests/flow-capabilities-module.test.js
index 4fa2bab..d745453 100644
--- a/tests/flow-capabilities-module.test.js
+++ b/tests/flow-capabilities-module.test.js
@@ -98,7 +98,7 @@ test('flow capability registry exposes Kiro as an independent flow with its own
assert.deepEqual(capabilityState.flowCapabilities.contributionAdapterIds, ['kiro-builder-id']);
assert.deepEqual(
capabilityState.visibleGroupIds,
- ['kiro-runtime-status', 'shared-auto-run', 'shared-settings-actions', 'kiro-target-kiro-rs', 'service-account', 'service-email', 'service-proxy']
+ ['kiro-runtime-status', 'shared-auto-run', 'kiro-target-kiro-rs', 'service-account', 'service-email', 'service-proxy']
);
});
@@ -128,7 +128,7 @@ test('flow capability registry exposes Grok as an independent SSO flow without O
assert.deepEqual(capabilityState.flowCapabilities.contributionAdapterIds, []);
assert.deepEqual(
capabilityState.visibleGroupIds,
- ['grok-runtime-status', 'shared-auto-run', 'shared-settings-actions', 'grok-target-webchat2api', 'service-account', 'service-email', 'service-proxy']
+ ['grok-runtime-status', 'shared-auto-run', 'grok-target-webchat2api', 'service-account', 'service-email', 'service-proxy']
);
});
diff --git a/tests/flow-registry-settings-schema.test.js b/tests/flow-registry-settings-schema.test.js
index 3857092..5daf037 100644
--- a/tests/flow-registry-settings-schema.test.js
+++ b/tests/flow-registry-settings-schema.test.js
@@ -46,15 +46,15 @@ test('flow registry exposes canonical flow and target metadata', () => {
assert.equal(flowRegistry.normalizeTargetId('grok', 'anything-else'), 'webchat2api');
assert.deepEqual(
flowRegistry.getVisibleGroupIds('openai', 'cpa'),
- ['openai-plus', 'openai-phone', 'shared-auto-run', 'openai-oauth', 'openai-step6', 'shared-settings-actions', 'openai-target-cpa', 'service-account', 'service-email', 'service-proxy']
+ ['openai-plus', 'openai-phone', 'shared-auto-run', 'openai-oauth', 'openai-step6', 'openai-target-cpa', 'service-account', 'service-email', 'service-proxy']
);
assert.deepEqual(
flowRegistry.getVisibleGroupIds('kiro', 'kiro-rs'),
- ['kiro-runtime-status', 'shared-auto-run', 'shared-settings-actions', 'kiro-target-kiro-rs', 'service-account', 'service-email', 'service-proxy']
+ ['kiro-runtime-status', 'shared-auto-run', 'kiro-target-kiro-rs', 'service-account', 'service-email', 'service-proxy']
);
assert.deepEqual(
flowRegistry.getVisibleGroupIds('grok', 'webchat2api'),
- ['grok-runtime-status', 'shared-auto-run', 'shared-settings-actions', 'grok-target-webchat2api', 'service-account', 'service-email', 'service-proxy']
+ ['grok-runtime-status', 'shared-auto-run', 'grok-target-webchat2api', 'service-account', 'service-email', 'service-proxy']
);
assert.deepEqual(
flowRegistry.getTargetOptions('openai').map((entry) => entry.id),
@@ -72,10 +72,6 @@ test('flow registry exposes canonical flow and target metadata', () => {
flowRegistry.getSettingsGroupDefinition('shared-auto-run')?.rowIds,
['row-shared-auto-run', 'row-auto-run-thread-interval', 'row-step-execution-range']
);
- assert.deepEqual(
- flowRegistry.getSettingsGroupDefinition('shared-settings-actions')?.rowIds,
- ['row-settings-actions']
- );
assert.equal(flowRegistry.getPublicationTargetDefinition('kiro', 'kiro-rs')?.label, 'kiro.rs');
assert.equal(flowRegistry.getFlowCapabilities('openai').supportsAccountContribution, true);
assert.equal(flowRegistry.getFlowCapabilities('kiro').supportsAccountContribution, true);
diff --git a/tests/sidepanel-auto-run-content-refresh.test.js b/tests/sidepanel-auto-run-content-refresh.test.js
index 87f8aa0..e4ac6ff 100644
--- a/tests/sidepanel-auto-run-content-refresh.test.js
+++ b/tests/sidepanel-auto-run-content-refresh.test.js
@@ -367,7 +367,7 @@ function clearTimeout(value) {
async function persistSignupPhoneInputForAction() {
phonePersistCalls += 1;
}
-function updateSaveButtonState() {}
+function updateSettingsSaveState() {}
function collectSettingsPayload() {
return { luckmailApiKey: 'autofilled-key' };
}
diff --git a/tests/sidepanel-flow-source-registry.test.js b/tests/sidepanel-flow-source-registry.test.js
index 804cb74..8dccd4d 100644
--- a/tests/sidepanel-flow-source-registry.test.js
+++ b/tests/sidepanel-flow-source-registry.test.js
@@ -43,7 +43,6 @@ test('sidepanel html exposes flow selector and kiro source fields', () => {
'id="row-shared-auto-run"',
'id="row-auto-run-thread-interval"',
'id="row-oauth-callback"',
- 'id="row-settings-actions"',
'id="row-kiro-rs-url"',
'id="btn-open-kiro-rs-github"',
'id="row-kiro-rs-key"',
diff --git a/tests/sidepanel-form-layout.test.js b/tests/sidepanel-form-layout.test.js
new file mode 100644
index 0000000..0adf4b5
--- /dev/null
+++ b/tests/sidepanel-form-layout.test.js
@@ -0,0 +1,117 @@
+const test = require('node:test');
+const assert = require('node:assert/strict');
+const fs = require('node:fs');
+
+function lineOf(source, index) {
+ return source.slice(0, index).split(/\r?\n/).length;
+}
+
+function readAttr(attrs, name) {
+ return attrs.match(new RegExp(`\\b${name}\\s*=\\s*(["'])(.*?)\\1`, 's'))?.[2] || '';
+}
+
+function hasClass(attrs, className) {
+ return readAttr(attrs, 'class').split(/\s+/).includes(className);
+}
+
+function collectDataRows(html) {
+ const tagRe = /<\/?([a-zA-Z][\w:-]*)([^>]*)>|/g;
+ const voidTags = new Set([
+ 'area',
+ 'base',
+ 'br',
+ 'col',
+ 'embed',
+ 'hr',
+ 'img',
+ 'input',
+ 'link',
+ 'meta',
+ 'param',
+ 'source',
+ 'track',
+ 'wbr',
+ ]);
+ const stack = [];
+ const rows = [];
+
+ for (const match of html.matchAll(tagRe)) {
+ const raw = match[0];
+ if (raw.startsWith('