diff --git a/.changeset/fix-mobile-add-case-submit.md b/.changeset/fix-mobile-add-case-submit.md
new file mode 100644
index 00000000..e15097e6
--- /dev/null
+++ b/.changeset/fix-mobile-add-case-submit.md
@@ -0,0 +1,16 @@
+---
+"aicodeman": patch
+---
+
+fix(mobile): give the Add Case modal a reachable submit button
+
+`mobile.css` hides `#createCaseModal`'s `.set-foot` on phones, and unlike the Settings
+modal that header carries no `set-head-save` — so the Create/Link button existed nowhere
+on a phone and the modal could not be submitted at all. Adds the header button and drives
+both together, so whichever one is pressed the other shows the same pending state and is
+equally unclickable.
+
+Add Case follows the Settings modal's header-save pattern, so it picks up the existing
+`.set-head-actions:has(.set-head-save)` tray and `.set-head-save` sizing below 860px with no
+new CSS; the `mobile.css` comment that still listed Add Case as a lone-× sheet is updated to
+match.
diff --git a/src/web/public/index.html b/src/web/public/index.html
index 78eff9d3..9391b9c2 100644
--- a/src/web/public/index.html
+++ b/src/web/public/index.html
@@ -2582,8 +2582,15 @@
diff --git a/src/web/public/mobile.css b/src/web/public/mobile.css
index c9b0838d..7dc2655c 100644
--- a/src/web/public/mobile.css
+++ b/src/web/public/mobile.css
@@ -3333,8 +3333,10 @@ html:is([data-skin="paper-gray"], [data-skin="solarized-light"], [data-skin="cat
recessed tray and matching pill geometry instead of reading as a fat
accent pill parked beside a stray × glyph. Tray colors come from skin
tokens, never a hardcoded black alpha, or the light skins get a grey slab.
- `:has()` keeps the tray off the two sheets that carry a lone × (Session
- Options and Add Case save from inside their own forms). */
+ `:has()` keeps the tray off Session Options, the one sheet left carrying a
+ lone × because it saves from inside its own per-section forms. Add Case
+ now has a header save of its own (its footer is hidden below 860px, so
+ that button is the only way to submit it there), and picks up the tray. */
:is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-head-actions:has(.set-head-save) {
padding: 3px;
border: 1px solid var(--border);
diff --git a/src/web/public/session-ui.js b/src/web/public/session-ui.js
index b4d18479..0ace6eef 100644
--- a/src/web/public/session-ui.js
+++ b/src/web/public/session-ui.js
@@ -2416,15 +2416,19 @@ Object.assign(CodemanApp.prototype, {
// A switched-to panel starts at its own top.
const doc = document.getElementById('createCaseDoc');
if (doc) doc.scrollTop = 0;
- // Update submit button (hide for manage tab)
- const submitBtn = document.getElementById('caseModalSubmit');
+ // Update submit buttons (hide for manage tab). Two of them: mobile.css hides
+ // this modal's .set-foot, so phones submit through the header button instead.
+ const submitBtns = ['caseModalSubmit', 'caseModalSubmitMobile']
+ .map((id) => document.getElementById(id))
+ .filter(Boolean);
if (tabName === 'case-manage') {
- submitBtn.style.display = 'none';
+ submitBtns.forEach((btn) => {
+ btn.style.display = 'none';
+ });
this.renderCaseManageList();
this.refreshDockerExports();
} else {
- submitBtn.style.display = '';
- submitBtn.textContent =
+ const label =
tabName === 'case-create'
? 'Create'
: tabName === 'case-clone'
@@ -2434,6 +2438,10 @@ Object.assign(CodemanApp.prototype, {
: tabName === 'case-docker'
? 'Link Docker'
: 'Link';
+ submitBtns.forEach((btn) => {
+ btn.style.display = '';
+ btn.textContent = label;
+ });
}
// Focus appropriate input
if (tabName === 'case-create') {
@@ -2454,14 +2462,19 @@ Object.assign(CodemanApp.prototype, {
},
async submitCaseModal() {
- const btn = document.getElementById('caseModalSubmit');
- const originalText = btn.textContent;
- btn.classList.add('loading');
- btn.textContent =
+ // Both submit buttons move together: whichever one the user pressed, the
+ // other must show the same pending state and be equally unclickable.
+ const btns = ['caseModalSubmit', 'caseModalSubmitMobile'].map((id) => document.getElementById(id)).filter(Boolean);
+ const originalText = btns.map((btn) => btn.textContent);
+ const pendingText =
this.caseModalTab === 'case-create' ? 'Creating...' : this.caseModalTab === 'case-clone' ? 'Cloning...' : 'Linking...';
// A clone holds this request open for minutes; without disabling the button a
// second click fires a second clone (the loser then fails on ALREADY_EXISTS).
- btn.disabled = true;
+ btns.forEach((btn) => {
+ btn.classList.add('loading');
+ btn.textContent = pendingText;
+ btn.disabled = true;
+ });
try {
if (this.caseModalTab === 'case-create') {
await this.createCase();
@@ -2475,9 +2488,11 @@ Object.assign(CodemanApp.prototype, {
await this.linkCase();
}
} finally {
- btn.classList.remove('loading');
- btn.disabled = false;
- btn.textContent = originalText;
+ btns.forEach((btn, index) => {
+ btn.classList.remove('loading');
+ btn.disabled = false;
+ btn.textContent = originalText[index];
+ });
}
},