Open a focused blocker flow from the active Today HUD #1071
|
|
@ -290,6 +290,8 @@ textarea { resize: vertical; min-height: 120px; }
|
|||
.today-progress-evidence .issue-evidence-note textarea { min-height:72px; }
|
||||
.today-progress-blocker { display:grid; gap:8px; margin-top:14px; padding:12px; border:1px solid #875f2a; border-radius:12px; background:#21180d; }
|
||||
.today-progress-blocker input, .today-progress-blocker button { box-sizing:border-box; width:100%; min-height:44px; }
|
||||
.today-blocker-presets { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:8px; }
|
||||
.today-blocker-presets button { min-width:0; padding-inline:6px; }
|
||||
.today-progress-blocker p { margin:0; }
|
||||
.today-progress-blocker .today-progress-blocker-recovery { padding:8px; border-radius:8px; background:#3a270d; color:#fde68a; font-weight:700; }
|
||||
.today-progress-actions { display:grid; grid-template-columns:1fr 1fr; gap:8px; margin-top:12px; }
|
||||
|
|
@ -297,6 +299,13 @@ textarea { resize: vertical; min-height: 120px; }
|
|||
.today-progress-panel .voice-conversation { margin-top:10px; }
|
||||
.today-progress-panel .voice-conversation-controls button,
|
||||
.today-progress-panel .voice-conversation-review-actions button { min-height:44px; }
|
||||
.today-progress-panel.blocker-mode .today-progress-activity,
|
||||
.today-progress-panel.blocker-mode .voice-conversation,
|
||||
.today-progress-panel.blocker-mode .today-progress-evidence,
|
||||
.today-progress-panel.blocker-mode .today-progress-update-help,
|
||||
.today-progress-panel.blocker-mode .today-progress-actions { display:none; }
|
||||
.today-progress-panel.blocker-mode textarea { min-height:112px; }
|
||||
@media(max-width:359px) { .today-blocker-presets { grid-template-columns:1fr; } }
|
||||
@media(max-width:359px) { .today-progress-actions { grid-template-columns:1fr; } }
|
||||
.today-recap-sheet { position:fixed; inset:0; z-index:88; display:flex; align-items:flex-end; justify-content:center; background:rgba(5,12,21,.82); backdrop-filter:blur(4px); }
|
||||
.today-recap-sheet[hidden] { display:none; }
|
||||
|
|
@ -1116,7 +1125,8 @@ textarea { resize: vertical; min-height: 120px; }
|
|||
.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 [data-mobile-today-update] { grid-column:1 / -1; width:100%; }
|
||||
.mobile-today-hud [data-mobile-today-update] { grid-column:1; width:100%; }
|
||||
.mobile-today-hud [data-mobile-today-blocked] { grid-column:2; width:100%; }
|
||||
.mobile-today-hud [data-today-break-open] { grid-column:1 / -1; }
|
||||
|
||||
.mobile-today-hud [data-work-session-adjust-plan] { grid-column:1 / -1; }
|
||||
|
|
|
|||
|
|
@ -1537,6 +1537,7 @@
|
|||
<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-update type="button" hidden>Add update</button>
|
||||
<button class="mobile-today-blocked" data-mobile-today-blocked type="button" hidden>Blocked</button>
|
||||
<button data-today-break-open type="button">Take break</button>
|
||||
<button data-work-session-adjust-plan type="button" hidden>Adjust remaining plan</button>
|
||||
</section>
|
||||
|
|
@ -1556,7 +1557,7 @@
|
|||
<button id="load-older-today-progress-activity" type="button" hidden>Load older</button>
|
||||
</div>
|
||||
</section>
|
||||
<label for="today-progress-body">Update</label>
|
||||
<label id="today-progress-body-label" for="today-progress-body">Update</label>
|
||||
<textarea id="today-progress-body" rows="5" maxlength="2000" placeholder="What changed, what you learned, or what comes next"></textarea>
|
||||
<div class="mention-options" id="today-progress-body-mentions" role="listbox" aria-label="Teammates" hidden></div>
|
||||
<div class="mention-status small" id="today-progress-body-mention-status" aria-live="polite"></div>
|
||||
|
|
@ -1600,12 +1601,17 @@
|
|||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<p class="small muted">Save privately on this device, or post to the exact Gitea item. Today keeps running either way.</p>
|
||||
<p class="small muted today-progress-update-help">Save privately on this device, or post to the exact Gitea item. Today keeps running either way.</p>
|
||||
<section class="today-progress-blocker" aria-labelledby="today-progress-blocker-title">
|
||||
<strong id="today-progress-blocker-title">Blocked?</strong>
|
||||
<p id="today-progress-blocker-recovery" class="today-progress-blocker-recovery small" role="status" hidden></p>
|
||||
<label for="today-blocker-return-at">Return this item to Later at</label>
|
||||
<input id="today-blocker-return-at" type="datetime-local" />
|
||||
<div class="today-blocker-presets" aria-label="Return time choices">
|
||||
<button data-today-blocker-preset="later-today" type="button">Later today</button>
|
||||
<button data-today-blocker-preset="tomorrow" type="button">Tomorrow</button>
|
||||
<button data-today-blocker-custom type="button">Choose time</button>
|
||||
</div>
|
||||
<button id="post-today-blocker" type="button">Post blocker & move on</button>
|
||||
<p class="small muted">Posts this update first, then defers the item and continues Today.</p>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -309,11 +309,15 @@ function mountTodayProgressActivity(qs, fetchJson, actionSource = {}, render) {
|
|||
return activity;
|
||||
}
|
||||
|
||||
function createTodayProgressView({ progress, currentTarget, qs, photos, voice, activity, mentions, announce = () => {}, onAdmitted = () => {}, moveOn = null }) {
|
||||
function createTodayProgressView({ progress, currentTarget, qs, photos, voice, activity, mentions, announce = () => {}, onAdmitted = () => {}, moveOn = null, now = () => new Date() }) {
|
||||
const sheet = qs('#today-progress-sheet');
|
||||
const panel = qs('.today-progress-panel');
|
||||
const title = qs('#today-progress-title');
|
||||
const body = qs('#today-progress-body');
|
||||
const bodyLabel = qs('#today-progress-body-label');
|
||||
const status = qs('#today-progress-status');
|
||||
const launcher = qs('[data-mobile-today-update]');
|
||||
const blockerLauncher = qs('[data-mobile-today-blocked]');
|
||||
const blockerButton = qs('#post-today-blocker');
|
||||
const blockerReturn = qs('#today-blocker-return-at');
|
||||
const blockerRecovery = qs('#today-progress-blocker-recovery');
|
||||
|
|
@ -342,6 +346,7 @@ function createTodayProgressView({ progress, currentTarget, qs, photos, voice, a
|
|||
const update = () => {
|
||||
const target = currentTarget();
|
||||
launcher.hidden = !target;
|
||||
if (blockerLauncher) blockerLauncher.hidden = !target;
|
||||
if (openedTarget && target?.identity !== openedTarget.identity) mentions?.dismiss?.();
|
||||
};
|
||||
const checkpoint = async () => {
|
||||
|
|
@ -362,28 +367,50 @@ function createTodayProgressView({ progress, currentTarget, qs, photos, voice, a
|
|||
activity?.close?.();
|
||||
mentions?.dismiss?.();
|
||||
if (sheet.open) sheet.close();
|
||||
panel?.classList?.remove('blocker-mode');
|
||||
openedTarget = null;
|
||||
};
|
||||
|
||||
launcher.addEventListener('click', async () => {
|
||||
const open = async blockerMode => {
|
||||
const target = currentTarget();
|
||||
if (!target) return;
|
||||
mentions?.dismiss?.();
|
||||
openedTarget = target;
|
||||
panel?.classList?.[blockerMode ? 'add' : 'remove']('blocker-mode');
|
||||
if (title) title.textContent = blockerMode ? 'Report blocker' : 'Add progress update';
|
||||
if (bodyLabel) bodyLabel.textContent = blockerMode ? 'Blocker description' : 'Update';
|
||||
qs('#today-progress-target').textContent = target.label + (target.title ? ' · ' + target.title : '');
|
||||
body.value = progress.load(target.identity);
|
||||
showBlockerRecovery(progress.blockerRecovery?.(target.identity));
|
||||
status.textContent = 'Restoring saved photo evidence…';
|
||||
sheet.showModal();
|
||||
try {
|
||||
await voice?.open?.(target.identity);
|
||||
await photos?.open?.(target);
|
||||
await activity?.open?.(target);
|
||||
status.textContent = '';
|
||||
if (!blockerMode) {
|
||||
status.textContent = 'Restoring saved photo evidence…';
|
||||
try {
|
||||
await voice?.open?.(target.identity);
|
||||
await photos?.open?.(target);
|
||||
await activity?.open?.(target);
|
||||
status.textContent = '';
|
||||
}
|
||||
catch (error) { status.textContent = error.message + ' You can retry by reopening this update.'; }
|
||||
}
|
||||
catch (error) { status.textContent = error.message + ' You can retry by reopening this update.'; }
|
||||
else status.textContent = '';
|
||||
body.focus();
|
||||
});
|
||||
};
|
||||
launcher.addEventListener('click', () => open(false));
|
||||
blockerLauncher?.addEventListener('click', () => open(true));
|
||||
|
||||
const setReturn = preset => {
|
||||
const date = new Date(now());
|
||||
if (preset === 'later-today') date.setHours(date.getHours() + 4);
|
||||
else {
|
||||
date.setDate(date.getDate() + 1);
|
||||
date.setHours(9, 0, 0, 0);
|
||||
}
|
||||
blockerReturn.value = localDateTime(date);
|
||||
};
|
||||
qs('[data-today-blocker-preset="later-today"]')?.addEventListener('click', () => setReturn('later-today'));
|
||||
qs('[data-today-blocker-preset="tomorrow"]')?.addEventListener('click', () => setReturn('tomorrow'));
|
||||
qs('[data-today-blocker-custom]')?.addEventListener('click', () => blockerReturn?.focus?.());
|
||||
qs('#cancel-today-progress').addEventListener('click', async () => {
|
||||
if (await checkpoint()) close();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -87,6 +87,26 @@ def test_release_artifact_plans_hands_off_and_opens_next_mobile_issue(tmp_path:
|
|||
if page.locator("#plan-today-sheet").is_visible():
|
||||
page.locator("#cancel-plan-today").click()
|
||||
expect(page.locator("[data-mobile-today-hud]")).to_be_visible()
|
||||
blocked = page.locator("[data-mobile-today-blocked]")
|
||||
expect(blocked).to_be_visible()
|
||||
blocked_bounds = blocked.bounding_box()
|
||||
assert blocked_bounds and blocked_bounds["height"] >= 44
|
||||
blocked.click()
|
||||
expect(page.locator("#today-progress-sheet")).to_be_visible()
|
||||
expect(page.locator("#today-progress-title")).to_have_text("Report blocker")
|
||||
expect(page.locator("#today-progress-target")).to_contain_text("Ship mobile capture")
|
||||
expect(page.locator("#today-progress-body")).to_be_focused()
|
||||
expect(page.locator("#today-progress-activity")).to_be_hidden()
|
||||
expect(page.locator(".today-progress-evidence")).to_be_hidden()
|
||||
for control in page.locator(".today-blocker-presets button, #post-today-blocker").all():
|
||||
bounds = control.bounding_box()
|
||||
assert bounds and bounds["height"] >= 44
|
||||
page.locator('[data-today-blocker-preset="later-today"]').click()
|
||||
assert page.locator("#today-blocker-return-at").input_value()
|
||||
assert page.evaluate("document.documentElement.scrollWidth <= window.innerWidth")
|
||||
page.locator("#cancel-today-progress").click()
|
||||
expect(page.locator("#today-progress-sheet")).to_be_hidden()
|
||||
expect(page.locator("[data-mobile-today-hud]")).to_contain_text("Ship mobile capture")
|
||||
fake.activity_comments = {
|
||||
41: [{
|
||||
"id": 701,
|
||||
|
|
|
|||
|
|
@ -329,6 +329,65 @@ const view=createView({{
|
|||
}
|
||||
|
||||
|
||||
def test_blocked_hud_opens_a_focused_flow_with_quick_return_choices():
|
||||
script = f"""
|
||||
const {{createView}}=require({json.dumps(str(TODAY_PROGRESS))});
|
||||
class Element {{
|
||||
constructor() {{
|
||||
this.hidden=false;this.value='';this.textContent='';this.open=false;this.focused=false;this.listeners={{}};
|
||||
const names=new Set();
|
||||
this.classList={{add:name=>names.add(name),remove:name=>names.delete(name),contains:name=>names.has(name)}};
|
||||
}}
|
||||
addEventListener(name,callback) {{ this.listeners[name]=callback; }}
|
||||
click() {{ return this.listeners.click?.(); }}
|
||||
showModal() {{ this.open=true; }} close() {{ this.open=false; }} focus() {{ this.focused=true; }}
|
||||
}}
|
||||
const selectors=['#today-progress-sheet','.today-progress-panel','#today-progress-title','#today-progress-body',
|
||||
'#today-progress-status','[data-mobile-today-update]','[data-mobile-today-blocked]',
|
||||
'#today-progress-target','#cancel-today-progress','#save-today-progress','#post-today-progress',
|
||||
'#today-blocker-return-at','#post-today-blocker','[data-today-blocker-preset="later-today"]',
|
||||
'[data-today-blocker-preset="tomorrow"]','[data-today-blocker-custom]'];
|
||||
const elements=Object.fromEntries(selectors.map(selector=>[selector,new Element()]));
|
||||
const target={{identity:'issue:stackchain/dashboard:1070:',kind:'issue',repository:'stackchain/dashboard',number:1070,label:'#1070',title:'Focused blocker'}};
|
||||
const calls=[];
|
||||
const view=createView({{
|
||||
progress:{{load:()=> 'Waiting for access',save:()=>true}},currentTarget:()=>target,qs:selector=>elements[selector],
|
||||
voice:{{open:async()=>calls.push('voice')}},photos:{{open:async()=>calls.push('photos')}},
|
||||
activity:{{open:async()=>calls.push('activity')}},now:()=>new Date('2026-08-18T10:15:00Z'),
|
||||
}});
|
||||
(async()=>{{
|
||||
const availability={{update:elements['[data-mobile-today-update]'].hidden,blocked:elements['[data-mobile-today-blocked]'].hidden}};
|
||||
await elements['[data-mobile-today-blocked]'].click();
|
||||
const opened={{
|
||||
title:elements['#today-progress-title'].textContent,target:elements['#today-progress-target'].textContent,
|
||||
focused:elements['#today-progress-body'].focused,mode:elements['.today-progress-panel'].classList.contains('blocker-mode'),
|
||||
calls:[...calls],sheet:elements['#today-progress-sheet'].open,
|
||||
}};
|
||||
await elements['[data-today-blocker-preset="later-today"]'].click();
|
||||
const laterToday=elements['#today-blocker-return-at'].value;
|
||||
await elements['[data-today-blocker-preset="tomorrow"]'].click();
|
||||
const tomorrow=elements['#today-blocker-return-at'].value;
|
||||
elements['#today-blocker-return-at'].focused=false;
|
||||
await elements['[data-today-blocker-custom]'].click();
|
||||
process.stdout.write(JSON.stringify({{availability,opened,laterToday,tomorrow,customFocused:elements['#today-blocker-return-at'].focused}}));
|
||||
}})().catch(error=>{{console.error(error);process.exit(1);}});
|
||||
"""
|
||||
assert run_node(script) == {
|
||||
"availability": {"update": False, "blocked": False},
|
||||
"opened": {
|
||||
"title": "Report blocker",
|
||||
"target": "#1070 · Focused blocker",
|
||||
"focused": True,
|
||||
"mode": True,
|
||||
"calls": [],
|
||||
"sheet": True,
|
||||
},
|
||||
"laterToday": "2026-08-18T14:15",
|
||||
"tomorrow": "2026-08-19T09:00",
|
||||
"customFocused": True,
|
||||
}
|
||||
|
||||
|
||||
def test_progress_sheet_dismisses_mentions_when_opened_switched_or_closed():
|
||||
script = f"""
|
||||
const {{createView}}=require({json.dumps(str(TODAY_PROGRESS))});
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user