From 7d1e0f895ca13860cbb87cddd1dea32d86515b7b Mon Sep 17 00:00:00 2001 From: Tyler Koenig Date: Wed, 20 May 2026 19:14:02 -0400 Subject: [PATCH] fix(web): mobile edit via inline fullscreen instead of hidden detail pane Detail pane is display:none on mobile, so edit mode was unreachable. Render edit fields directly in the inline expansion with exp-full takeover. ESC and Cmd+Enter work from within inputs. Closes #32 --- web/app.js | 67 +++++++++++++++++++++++++++++++++++++++++++++++++-- web/style.css | 4 +++ 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/web/app.js b/web/app.js index 25b07e7..ade4b6b 100644 --- a/web/app.js +++ b/web/app.js @@ -658,6 +658,25 @@ `; } + function renderInlineEditMode(e) { + return `
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
`; + } + function renderInlineDetail(e) { const tags = (e.tags || []).map(t => `#${t}`).join(''); let actions = ''; @@ -1457,11 +1476,31 @@ state.peekMode = mode; if (mode === 'run') state.runChecked = new Set(); if (mode === 'fill') { state.fillValues = {}; state.fillActive = 0; } + if (mode === 'edit' && isMobileBreakpoint()) { + const e = state.entities[state.selectedIndex]; + const sel = $(`.entity-item.selected, .card-row.selected`); + if (!e || !sel) return; + const clip = sel.querySelector('.entity-exp-clip'); + if (clip) clip.innerHTML = renderInlineEditMode(e); + sel.classList.add('exp-full'); + const titleInput = sel.querySelector('#edit-title'); + if (titleInput) titleInput.focus(); + return; + } renderDetailPane(); }, exitMode() { state.peekMode = 'preview'; + if (isMobileBreakpoint()) { + const e = state.entities[state.selectedIndex]; + const sel = $(`.entity-item.selected, .card-row.selected`); + if (sel && e) { + const clip = sel.querySelector('.entity-exp-clip'); + if (clip) clip.innerHTML = renderInlineDetail(e); + } + return; + } renderDetailPane(); }, @@ -1500,7 +1539,21 @@ await loadEntities(); await loadTags(); const idx = state.entities.findIndex(x => x.id === id); - if (idx >= 0) selectEntity(idx); + if (idx >= 0) { + if (isMobileBreakpoint()) { + state.selectedIndex = idx; + renderEntityList(); + const sel = $(`.entity-item[data-id="${id}"], .card-row[data-id="${id}"]`); + if (sel) { + sel.classList.add('selected'); + const clip = sel.querySelector('.entity-exp-clip'); + const e = state.entities[idx]; + if (clip && e) clip.innerHTML = renderInlineDetail(e); + } + } else { + selectEntity(idx); + } + } showToast('saved'); }, @@ -1580,7 +1633,17 @@ document.addEventListener('keydown', (ev) => { const tag = (ev.target.tagName || '').toLowerCase(); if (tag === 'input' || tag === 'textarea') { - if (ev.key === 'Escape') ev.target.blur(); + if (ev.key === 'Escape' && state.peekMode === 'edit') { + ev.target.blur(); + nibApp.exitMode(); + return; + } + if (ev.key === 'Escape') { ev.target.blur(); return; } + if ((ev.metaKey || ev.ctrlKey) && ev.key === 'Enter' && state.peekMode === 'edit') { + const e = state.entities[state.selectedIndex]; + if (e) nibApp.saveEdit(e.id); + return; + } return; } diff --git a/web/style.css b/web/style.css index 6406254..1018497 100644 --- a/web/style.css +++ b/web/style.css @@ -1859,5 +1859,9 @@ kbd { background: var(--raised); border: 1px solid var(--border); border-radius: .card-row.exp-full .entity-exp { grid-template-rows: 1fr; } .entity-item.exp-full .exp-inner, .card-row.exp-full .exp-inner { padding-top: 1rem; padding-bottom: 2rem; } + .exp-inner--edit { display: flex; flex-direction: column; min-height: 100%; } + .exp-inner--edit .peek-edit-fields { flex: 1; padding: 16px; } + .exp-inner--edit .peek-edit-ta { flex: 1; min-height: 150px; } + .exp-inner--edit .exp-acts { padding: 12px 16px; border-top: 1px solid var(--border); position: sticky; bottom: 0; background: var(--bg); } main.focus-peek #entity-panel { display: block; overflow: auto; min-width: 0; } } -- 2.52.0