Compare commits
No commits in common. "8dc155e49aebb7e3dfff89c6dfa2116ae0f8eb02" and "f02341492936351d5dbfcb75d14cf7acd6148422" have entirely different histories.
8dc155e49a
...
f023414929
|
|
@ -574,11 +574,9 @@ textarea { resize: vertical; min-height: 120px; }
|
||||||
.composer-keyboard-active .update-reply { scroll-margin-block:12px; padding-bottom:env(safe-area-inset-bottom); }
|
.composer-keyboard-active .update-reply { scroll-margin-block:12px; padding-bottom:env(safe-area-inset-bottom); }
|
||||||
.mobile-task-dock { position:fixed; inset:auto 0 0; z-index:45; display:grid; grid-template-columns:repeat(5,minmax(0,1fr)); gap:2px; padding:6px 8px; padding-bottom:env(safe-area-inset-bottom); border-top:1px solid #2a496e; background:rgba(11,21,38,.98); backdrop-filter:blur(12px); }
|
.mobile-task-dock { position:fixed; inset:auto 0 0; z-index:45; display:grid; grid-template-columns:repeat(5,minmax(0,1fr)); gap:2px; padding:6px 8px; padding-bottom:env(safe-area-inset-bottom); border-top:1px solid #2a496e; background:rgba(11,21,38,.98); backdrop-filter:blur(12px); }
|
||||||
.mobile-task-dock[data-attention="true"] { grid-template-columns:repeat(6,minmax(0,1fr)); }
|
.mobile-task-dock[data-attention="true"] { grid-template-columns:repeat(6,minmax(0,1fr)); }
|
||||||
.mobile-today-hud { position:fixed; left:8px; right:8px; bottom:calc(56px + env(safe-area-inset-bottom)); z-index:44; display:grid; grid-template-columns:minmax(0,1fr) minmax(112px,auto); grid-template-areas:"summary complete" "progress toggle"; gap:4px 8px; max-width:100%; padding:8px; border:1px solid #31577f; border-radius:12px 12px 0 0; background:rgba(16,38,65,.98); box-shadow:0 -8px 24px rgba(0,0,0,.28); }
|
.mobile-today-hud { position:fixed; left:8px; right:8px; bottom:calc(56px + env(safe-area-inset-bottom)); z-index:44; display:grid; grid-template-columns:minmax(0,1fr) auto; gap:4px 8px; max-width:100%; padding:8px; border:1px solid #31577f; border-radius:12px 12px 0 0; background:rgba(16,38,65,.98); box-shadow:0 -8px 24px rgba(0,0,0,.28); }
|
||||||
.mobile-today-summary { grid-area:summary; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; text-align:left; font-weight:700; }
|
.mobile-today-summary { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; text-align:left; font-weight:700; }
|
||||||
.mobile-today-hud [data-work-session-progress] { grid-area:progress; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
.mobile-today-hud [data-work-session-progress] { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
||||||
.mobile-today-hud [data-mobile-today-complete] { grid-area:complete; }
|
|
||||||
.mobile-today-hud [data-mobile-today-toggle] { grid-area:toggle; }
|
|
||||||
.mobile-today-hud button { min-height:44px; max-width:100%; }
|
.mobile-today-hud button { min-height:44px; max-width:100%; }
|
||||||
.mobile-today-hud [data-work-session-adjust-plan] { grid-column:1 / -1; }
|
.mobile-today-hud [data-work-session-adjust-plan] { grid-column:1 / -1; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -955,10 +955,6 @@
|
||||||
selectTodayWork();
|
selectTodayWork();
|
||||||
workSession.reopen(todayMyWork.find(item => todayWork.identity(item) === identity));
|
workSession.reopen(todayMyWork.find(item => todayWork.identity(item) === identity));
|
||||||
},
|
},
|
||||||
onComplete: identity => {
|
|
||||||
const item = todayMyWork.find(entry => todayWork.identity(entry) === identity);
|
|
||||||
return completeTodayItem(item);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
renderAttentionInterruption();
|
renderAttentionInterruption();
|
||||||
qs('#return-to-today').addEventListener('click', () => {
|
qs('#return-to-today').addEventListener('click', () => {
|
||||||
|
|
|
||||||
|
|
@ -813,7 +813,6 @@
|
||||||
|
|
||||||
<section class="mobile-today-hud" data-mobile-today-hud aria-label="Active Today session" hidden>
|
<section class="mobile-today-hud" data-mobile-today-hud aria-label="Active Today session" hidden>
|
||||||
<button class="mobile-today-summary" data-mobile-today-open type="button" aria-label="Open current Today item"></button>
|
<button class="mobile-today-summary" data-mobile-today-open type="button" aria-label="Open current Today item"></button>
|
||||||
<button data-mobile-today-complete type="button" hidden>Done & next</button>
|
|
||||||
<div class="small" data-work-session-progress aria-live="polite"></div>
|
<div class="small" data-work-session-progress aria-live="polite"></div>
|
||||||
<button data-mobile-today-toggle data-work-session-timer-toggle type="button">Pause timer</button>
|
<button data-mobile-today-toggle data-work-session-timer-toggle type="button">Pause timer</button>
|
||||||
<button data-work-session-adjust-plan type="button" hidden>Adjust remaining plan</button>
|
<button data-work-session-adjust-plan type="button" hidden>Adjust remaining plan</button>
|
||||||
|
|
|
||||||
|
|
@ -200,7 +200,7 @@ function createTodayTimer({ storage, getLogin, now = () => Date.now() }) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function createTodayTimerView({ timer, isActive, queryAll, formatEstimate, getRunway, getItem, onReopen, onComplete }) {
|
function createTodayTimerView({ timer, isActive, queryAll, formatEstimate, getRunway, getItem, onReopen }) {
|
||||||
let progress = null;
|
let progress = null;
|
||||||
let runway = null;
|
let runway = null;
|
||||||
queryAll('[data-mobile-today-open]').forEach(button =>
|
queryAll('[data-mobile-today-open]').forEach(button =>
|
||||||
|
|
@ -212,12 +212,6 @@ function createTodayTimerView({ timer, isActive, queryAll, formatEstimate, getRu
|
||||||
queryAll('[data-mobile-today-toggle]').forEach(button =>
|
queryAll('[data-mobile-today-toggle]').forEach(button =>
|
||||||
button.addEventListener('click', () => view.toggle())
|
button.addEventListener('click', () => view.toggle())
|
||||||
);
|
);
|
||||||
queryAll('[data-mobile-today-complete]').forEach(button =>
|
|
||||||
button.addEventListener('click', () => {
|
|
||||||
const identity = timer.snapshot().identity;
|
|
||||||
if (identity) onComplete?.(identity);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
if (typeof document !== 'undefined') queryAll('.work-session-nav').forEach(nav => {
|
if (typeof document !== 'undefined') queryAll('.work-session-nav').forEach(nav => {
|
||||||
const button = document.createElement('button');
|
const button = document.createElement('button');
|
||||||
button.type = 'button';
|
button.type = 'button';
|
||||||
|
|
@ -253,15 +247,6 @@ function createTodayTimerView({ timer, isActive, queryAll, formatEstimate, getRu
|
||||||
queryAll('[data-mobile-today-open]').forEach(element => {
|
queryAll('[data-mobile-today-open]').forEach(element => {
|
||||||
element.textContent = active ? String(getItem?.(snapshot.identity)?.title || 'Current Today item') : '';
|
element.textContent = active ? String(getItem?.(snapshot.identity)?.title || 'Current Today item') : '';
|
||||||
});
|
});
|
||||||
queryAll('[data-mobile-today-complete]').forEach(button => {
|
|
||||||
const itemTitle = String(getItem?.(snapshot.identity)?.title || 'current Today item');
|
|
||||||
const finalItem = Boolean(progress && progress.index >= progress.total);
|
|
||||||
button.hidden = !active;
|
|
||||||
button.textContent = finalItem ? 'Done & recap' : 'Done & next';
|
|
||||||
button.setAttribute('aria-label', finalItem ?
|
|
||||||
'Complete ' + itemTitle + ' and open Today recap' :
|
|
||||||
'Complete ' + itemTitle + ' and open next Today item');
|
|
||||||
});
|
|
||||||
if (typeof document !== 'undefined') document.body.classList.toggle('mobile-today-active', active);
|
if (typeof document !== 'undefined') document.body.classList.toggle('mobile-today-active', active);
|
||||||
if (!progress) return;
|
if (!progress) return;
|
||||||
const sourceRunway = getRunway?.(snapshot) || runway;
|
const sourceRunway = getRunway?.(snapshot) || runway;
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ from tests.dashboard_bundle import dashboard
|
||||||
|
|
||||||
DOCK = Path(__file__).resolve().parents[1] / "frontend" / "mobile-task-dock.js"
|
DOCK = Path(__file__).resolve().parents[1] / "frontend" / "mobile-task-dock.js"
|
||||||
ENTRY = Path(__file__).resolve().parents[1] / "frontend" / "mobile-work-entry.js"
|
ENTRY = Path(__file__).resolve().parents[1] / "frontend" / "mobile-work-entry.js"
|
||||||
TIMER = Path(__file__).resolve().parents[1] / "frontend" / "today-timer.js"
|
|
||||||
|
|
||||||
|
|
||||||
def test_mobile_work_entry_prioritizes_continue_resume_start_then_fallback():
|
def test_mobile_work_entry_prioritizes_continue_resume_start_then_fallback():
|
||||||
|
|
@ -147,67 +146,6 @@ process.stdout.write(JSON.stringify({{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_mobile_today_hud_completes_current_item_and_labels_final_item_for_recap():
|
|
||||||
script = f"""
|
|
||||||
const createView = require({json.dumps(str(TIMER))}).createView;
|
|
||||||
class FakeElement {{
|
|
||||||
constructor() {{ this.listeners = {{}}; this.hidden = true; this.textContent = ''; this.attributes = {{}}; }}
|
|
||||||
addEventListener(name, callback) {{ this.listeners[name] = callback; }}
|
|
||||||
click() {{ return this.listeners.click?.(); }}
|
|
||||||
setAttribute(name, value) {{ this.attributes[name] = value; }}
|
|
||||||
}}
|
|
||||||
const hud = new FakeElement();
|
|
||||||
const open = new FakeElement();
|
|
||||||
const toggle = new FakeElement();
|
|
||||||
const complete = new FakeElement();
|
|
||||||
const progress = new FakeElement();
|
|
||||||
const elements = {{
|
|
||||||
'[data-mobile-today-hud]':[hud],
|
|
||||||
'[data-mobile-today-open]':[open],
|
|
||||||
'[data-mobile-today-toggle]':[toggle],
|
|
||||||
'[data-mobile-today-complete]':[complete],
|
|
||||||
'[data-work-session-progress]':[progress],
|
|
||||||
'[data-work-session-adjust-plan]':[],
|
|
||||||
'[data-work-session-timer-toggle]':[toggle],
|
|
||||||
}};
|
|
||||||
let active = true;
|
|
||||||
const snapshot = {{identity:'issue:r:1:', elapsed_ms:0, running:true}};
|
|
||||||
const completed = [];
|
|
||||||
const view = createView({{
|
|
||||||
timer:{{snapshot:()=>snapshot,totalElapsed:()=>0,pause:()=>true,resume:()=>true}},
|
|
||||||
isActive:()=>active,
|
|
||||||
queryAll:selector=>elements[selector] || [],
|
|
||||||
formatEstimate:value=>value + 'm',
|
|
||||||
getItem:()=>({{title:'Ship mobile flow'}}),
|
|
||||||
onComplete:identity=>{{ completed.push(identity); return false; }},
|
|
||||||
}});
|
|
||||||
view.update({{index:1,total:2}}, null);
|
|
||||||
const next = {{hidden:complete.hidden,label:complete.textContent,aria:complete.attributes['aria-label']}};
|
|
||||||
complete.click();
|
|
||||||
const preservedAfterFailure = {{hudHidden:hud.hidden,identity:snapshot.identity}};
|
|
||||||
view.update({{index:2,total:2}}, null);
|
|
||||||
const recap = {{hidden:complete.hidden,label:complete.textContent}};
|
|
||||||
active = false; view.render();
|
|
||||||
process.stdout.write(JSON.stringify({{next,recap,completed,preservedAfterFailure,hiddenWhenInactive:complete.hidden}}));
|
|
||||||
"""
|
|
||||||
result = subprocess.run(
|
|
||||||
["node", "-e", script], capture_output=True, text=True
|
|
||||||
)
|
|
||||||
|
|
||||||
assert result.returncode == 0, result.stderr
|
|
||||||
assert json.loads(result.stdout) == {
|
|
||||||
"next": {
|
|
||||||
"hidden": False,
|
|
||||||
"label": "Done & next",
|
|
||||||
"aria": "Complete Ship mobile flow and open next Today item",
|
|
||||||
},
|
|
||||||
"recap": {"hidden": False, "label": "Done & recap"},
|
|
||||||
"completed": ["issue:r:1:"],
|
|
||||||
"preservedAfterFailure": {"hudHidden": False, "identity": "issue:r:1:"},
|
|
||||||
"hiddenWhenInactive": True,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def test_mobile_task_dock_exposes_independent_attention_action_and_six_column_state():
|
def test_mobile_task_dock_exposes_independent_attention_action_and_six_column_state():
|
||||||
script = f"""
|
script = f"""
|
||||||
const createDock = require({json.dumps(str(DOCK))});
|
const createDock = require({json.dumps(str(DOCK))});
|
||||||
|
|
@ -293,16 +231,10 @@ async def test_dashboard_renders_phone_safe_today_session_hud_above_task_dock():
|
||||||
assert 'data-work-session-progress' in html
|
assert 'data-work-session-progress' in html
|
||||||
assert 'data-work-session-timer-toggle' in html
|
assert 'data-work-session-timer-toggle' in html
|
||||||
assert 'data-work-session-adjust-plan' in html
|
assert 'data-work-session-adjust-plan' in html
|
||||||
assert 'data-mobile-today-complete' in html
|
|
||||||
assert 'Done & next' in html
|
|
||||||
assert "getItem: identity => [...todayMyWork, ...activeMyWork].find" in html
|
assert "getItem: identity => [...todayMyWork, ...activeMyWork].find" in html
|
||||||
assert "onReopen: identity =>" in html
|
assert "onReopen: identity =>" in html
|
||||||
assert "onComplete: identity =>" in html
|
|
||||||
assert "completeTodayItem(item)" in html
|
|
||||||
assert "workSession.reopen(item)" in html
|
assert "workSession.reopen(item)" in html
|
||||||
assert '.mobile-today-hud { display:none;' in html
|
assert '.mobile-today-hud { display:none;' in html
|
||||||
assert 'grid-template-areas:' in html
|
|
||||||
assert '[data-mobile-today-complete] { grid-area:complete;' in html
|
|
||||||
assert 'bottom:calc(56px + env(safe-area-inset-bottom))' in html
|
assert 'bottom:calc(56px + env(safe-area-inset-bottom))' in html
|
||||||
assert '.mobile-today-hud button { min-height:44px;' in html
|
assert '.mobile-today-hud button { min-height:44px;' in html
|
||||||
assert '.mobile-task-action[hidden] { display:none;' in html
|
assert '.mobile-task-action[hidden] { display:none;' in html
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user