ui: focus board on live work
This commit is contained in:
@@ -76,10 +76,12 @@ catalogue.
|
||||
leads with reason, last activity, pane state, retained evidence, and the
|
||||
next safe action. Only server-enabled lifecycle actions are shown directly;
|
||||
unavailable actions are kept in an “Unavailable actions” disclosure.
|
||||
4. **Separate active work from history.** Default the board to active,
|
||||
waiting, and needs-attention work; move completed/failed/test residue to
|
||||
filters or a compact history view. Add search, project filtering, and an
|
||||
explicit “no live work” state.
|
||||
4. ~~**Separate active work from history.**~~ **Closed in code,
|
||||
deployment verification outstanding (2026-07-29).** The board defaults to
|
||||
queued, leased, and blocked work; completed and failed records are in a
|
||||
History view, with an All view for audits. Search, project filtering, and
|
||||
an explicit no-live-work state keep historical E2E residue out of normal
|
||||
operations.
|
||||
5. **Complete the worker truth model.** Show worker-owned heartbeat, local
|
||||
herdr reachability, active task/pane, and last error separately from legacy
|
||||
coordinator probes.
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
@@ -1,3 +1,3 @@
|
||||
<script type="module" crossorigin src="/assets/index-BlmzmqVL.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-C0JjPpb-.css">
|
||||
<script type="module" crossorigin src="/assets/index-D1Up3m4b.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DUgNKGY0.css">
|
||||
<div id="root"></div>
|
||||
|
||||
+3
-2
@@ -8,6 +8,7 @@ import './style.css'
|
||||
|
||||
const client=new QueryClient()
|
||||
const states:TaskState[]=['queued','leased','completed','failed']
|
||||
const activeStates:TaskState[]=['queued','leased','blocked']
|
||||
const label:Record<TaskState,string>={queued:'Queued',leased:'In session',blocked:'Blocked',completed:'Complete',failed:'Failed'}
|
||||
const blockLabel:Record<BlockReason,string>={lease_failure:'Lease failure',worker_offline:'Worker offline',lease_expired:'Expired lease',approval:'Approval needed',handoff_validation:'Handoff validation',operator_block:'Operator block',system_error:'System error',unknown:'Unknown'}
|
||||
const actionLabel:Record<string,string>={handoff:'Request handoff',release:'Release task',complete:'Complete task',block:'Mark blocked'}
|
||||
@@ -19,9 +20,9 @@ function Shell({children,onLogout}:{children:React.ReactNode;onLogout:()=>void})
|
||||
|
||||
function taskExplanation(task:Task,overview:Overview){const session=(overview.sessions??[]).find(s=>s.capture?.task_id===task.id);if(task.state==='queued'){const online=(overview.workers??[]).filter(w=>w.online);return online.length?`Waiting for a worker to lease it · ${online.length} worker${online.length===1?'':'s'} online`:'No registered worker is currently reachable'}if(task.state==='leased'){if(session?.pending_approval)return 'Waiting for an operator approval';if(task.lease&&new Date(task.lease.until)<new Date())return 'Lease has expired; waiting for reconciliation';if(session?.blocker)return session.blocker;return session?.capture?'Harness session is active':'Lease is active; live capture is unavailable'}if(task.state==='blocked'){const evidence=task.last_session;const observed=evidence?.captured_at||evidence?.checked_at;const suffix=evidence?` · ${evidence.source||'unknown'} observed ${date(observed)}`:'';return (task.blocker||'Unknown — legacy record has no retained blocker or pane evidence')+suffix}if(task.state==='failed')return 'The task needs review before it can be retried';return 'Terminal task record'}
|
||||
function TaskCard({task,overview}:{task:Task;overview:Overview}){return <Link className="card" to={'/tasks/'+task.id}><b>{task.title||task.id}</b><small><span>{task.project}</span><span className="machine">{task.id.slice(-5)}</span></small><em>{taskExplanation(task,overview)}</em></Link>}
|
||||
function Board({tasks,overview}:{tasks:Task[];overview:Overview}){const blocked=tasks.filter(t=>t.state==='blocked').sort((a,b)=>new Date(a.blocked_at||0).getTime()-new Date(b.blocked_at||0).getTime());const groups=new Map<BlockReason,Task[]>;for(const task of blocked){const reason=task.block_reason||'unknown';groups.set(reason,[...(groups.get(reason)||[]),task])}return <><div className="board">{states.map(state=>{const lane=tasks.filter(t=>t.state===state);return <section key={state}><div className="lane-head"><span>{label[state]}</span><span className="lane-count">{lane.length}</span></div>{lane.map(t=><TaskCard task={t} overview={overview} key={t.id}/>)}{!lane.length&&<p className="empty-lane">Nothing here. New work will appear in this branch.</p>}</section>})}</div>{blocked.length>0&&<section className="blocked-groups" aria-label="Blocked work by diagnosis"><div className="section-title"><div><h2>Needs attention</h2><p>Blocked work is grouped by diagnosis and ordered oldest first.</p></div><span className="machine">{blocked.length} blocked</span></div><div className="blocked-grid">{[...groups.entries()].map(([reason,group])=><section key={reason}><div className="lane-head"><span>{blockLabel[reason]}</span><span className="lane-count">{group.length}</span></div>{group.map(task=><TaskCard task={task} overview={overview} key={task.id}/>)}</section>)}</div></section>}</>}
|
||||
function Board({tasks,overview,empty}:{tasks:Task[];overview:Overview;empty:string}){const blocked=tasks.filter(t=>t.state==='blocked').sort((a,b)=>new Date(a.blocked_at||0).getTime()-new Date(b.blocked_at||0).getTime());const groups=new Map<BlockReason,Task[]>;for(const task of blocked){const reason=task.block_reason||'unknown';groups.set(reason,[...(groups.get(reason)||[]),task])}if(!tasks.length)return <section className="empty-panel"><i className="branch-mark"/><h2>No matching work</h2><p>{empty}</p></section>;const boardStates=states.filter(state=>tasks.some(t=>t.state===state));return <><div className="board">{boardStates.map(state=>{const lane=tasks.filter(t=>t.state===state);return <section key={state}><div className="lane-head"><span>{label[state]}</span><span className="lane-count">{lane.length}</span></div>{lane.map(t=><TaskCard task={t} overview={overview} key={t.id}/>)}</section>})}</div>{blocked.length>0&&<section className="blocked-groups" aria-label="Blocked work by diagnosis"><div className="section-title"><div><h2>Needs attention</h2><p>Blocked work is grouped by diagnosis and ordered oldest first.</p></div><span className="machine">{blocked.length} blocked</span></div><div className="blocked-grid">{[...groups.entries()].map(([reason,group])=><section key={reason}><div className="lane-head"><span>{blockLabel[reason]}</span><span className="lane-count">{group.length}</span></div>{group.map(task=><TaskCard task={task} overview={overview} key={task.id}/>)}</section>)}</div></section>}</>}
|
||||
|
||||
function OverviewPage(){const q=useQuery({queryKey:['overview'],queryFn:api.overview,refetchInterval:5000}),[createOpen,setCreateOpen]=useState(false);useEffect(()=>{const open=()=>setCreateOpen(true);window.addEventListener('orchestra:new-task',open);return()=>window.removeEventListener('orchestra:new-task',open)},[]);if(q.isLoading)return <main className="page loading"><p className="eyebrow">Dispatch board</p><p>Fetching queue state from the coordinator…</p></main>;if(q.error)return <main className="page error" role="alert">Queue unavailable: {String(q.error)}</main>;const d=q.data!,sessions=d.sessions??[],active=d.tasks.filter(t=>t.state==='leased').length,attention=d.tasks.filter(t=>t.state==='blocked'||t.state==='failed').length,approvals=new Set(sessions.filter(s=>s.pending_approval).map(s=>s.capture?.task_id));return <main className="page"><div className="page-heading"><div><p className="eyebrow">Agent dispatch</p><h1>Keep the work moving.</h1></div><div className="heading-actions"><p>Live task state across every connected harness. The board refreshes every five seconds.</p><button onClick={()=>setCreateOpen(true)}>New task <kbd>N</kbd></button></div></div><div className="status-strip" aria-label="Queue summary"><div><span>Tasks</span><b>{d.tasks.length}</b></div><div><span>In session</span><b>{active}</b></div><div className={attention?'attention':''}><span>Needs attention</span><b>{attention}</b></div><div className={approvals.size?'attention':''}><span>Awaiting approval</span><b>{approvals.size}</b></div></div><div className="section-title"><div><h2>Task flow</h2><p>Each card states the current reason it is waiting or running.</p></div><span className="machine" aria-live="polite">SYNCED {new Date(d.updated_at).toLocaleTimeString()}</span></div><Board tasks={d.tasks} overview={d}/>{createOpen&&<Create overview={d} close={()=>setCreateOpen(false)}/>}</main>}
|
||||
function OverviewPage(){const q=useQuery({queryKey:['overview'],queryFn:api.overview,refetchInterval:5000}),[createOpen,setCreateOpen]=useState(false),[view,setView]=useState<'active'|'history'|'all'>('active'),[project,setProject]=useState(''),[search,setSearch]=useState('');useEffect(()=>{const open=()=>setCreateOpen(true);window.addEventListener('orchestra:new-task',open);return()=>window.removeEventListener('orchestra:new-task',open)},[]);if(q.isLoading)return <main className="page loading"><p className="eyebrow">Dispatch board</p><p>Fetching queue state from the coordinator…</p></main>;if(q.error)return <main className="page error" role="alert">Queue unavailable: {String(q.error)}</main>;const d=q.data!,sessions=d.sessions??[],active=d.tasks.filter(t=>t.state==='leased').length,attention=d.tasks.filter(t=>t.state==='blocked'||t.state==='failed').length,approvals=new Set(sessions.filter(s=>s.pending_approval).map(s=>s.capture?.task_id)),projects=[...new Set(d.tasks.map(t=>t.project).filter(Boolean))].sort(),term=search.trim().toLowerCase(),filtered=d.tasks.filter(t=>(!project||t.project===project)&&(!term||[t.id,t.title,t.description,t.project,t.blocker].filter(Boolean).join(' ').toLowerCase().includes(term))).filter(t=>view==='all'||view==='active'?activeStates.includes(t.state):!activeStates.includes(t.state)),history=d.tasks.filter(t=>!activeStates.includes(t.state)).length,empty=view==='active'?'There is no live work right now. Completed and failed records are available in History.':view==='history'?'No completed or failed records match these filters.':'No task records match these filters.';return <main className="page"><div className="page-heading"><div><p className="eyebrow">Agent dispatch</p><h1>Keep the work moving.</h1></div><div className="heading-actions"><p>Live task state across every connected harness. The board refreshes every five seconds.</p><button onClick={()=>setCreateOpen(true)}>New task <kbd>N</kbd></button></div></div><div className="status-strip" aria-label="Queue summary"><div><span>Tasks</span><b>{d.tasks.length}</b></div><div><span>In session</span><b>{active}</b></div><div className={attention?'attention':''}><span>Needs attention</span><b>{attention}</b></div><div className={approvals.size?'attention':''}><span>Awaiting approval</span><b>{approvals.size}</b></div></div><div className="section-title"><div><h2>Task flow</h2><p>{view==='active'?'Live, waiting, and needs-attention work.':view==='history'?'Completed and failed task records.':'All matching task records.'}</p></div><span className="machine" aria-live="polite">SYNCED {new Date(d.updated_at).toLocaleTimeString()}</span></div><div className="board-filters" aria-label="Task board filters"><div className="view-tabs" role="group" aria-label="Task view"><button className={view==='active'?'selected':''} onClick={()=>setView('active')}>Live work</button><button className={view==='history'?'selected':''} onClick={()=>setView('history')}>History {history>0&&<span>{history}</span>}</button><button className={view==='all'?'selected':''} onClick={()=>setView('all')}>All</button></div><label className="sr-only" htmlFor="task-search">Search tasks</label><input id="task-search" value={search} onChange={e=>setSearch(e.target.value)} placeholder="Search tasks"/><label className="sr-only" htmlFor="project-filter">Filter by project</label><select id="project-filter" value={project} onChange={e=>setProject(e.target.value)}><option value="">All projects</option>{projects.map(p=><option value={p} key={p}>{p}</option>)}</select></div><Board tasks={filtered} overview={d} empty={empty}/>{createOpen&&<Create overview={d} close={()=>setCreateOpen(false)}/>}</main>}
|
||||
|
||||
function Create({overview,close}:{overview:Overview;close:()=>void}){const qc=useQueryClient(),nav=useNavigate(),first=useRef<HTMLInputElement>(null),[title,setTitle]=useState(''),[description,setDescription]=useState(''),[project,setProject]=useState(''),[capability,setCapability]=useState(''),[advanced,setAdvanced]=useState(''),[formError,setFormError]=useState('');const projects=[...new Set(overview.tasks.map(t=>t.project).filter(Boolean))];const online=overview.workers.filter(w=>w.online);useEffect(()=>{first.current?.focus()},[]);const m=useMutation({mutationFn:()=>{let extra:Record<string,unknown>={};if(advanced.trim()){try{extra=JSON.parse(advanced)}catch{return Promise.reject(new Error('Additional fields must be valid JSON.'))}}return api.create({...extra,source:'web',external_id:crypto.randomUUID(),project,title,description,capability:capability.split(',').map(x=>x.trim()).filter(Boolean)})},onSuccess:(e:any)=>{qc.invalidateQueries({queryKey:['overview']});nav('/tasks/'+e.task_id)}});const submit=(e:React.FormEvent)=>{e.preventDefault();setFormError('');if(title.trim().length<3){setFormError('Give the task a title of at least three characters.');return}if(!project.trim()){setFormError('Choose an existing project or enter its exact project ID.');return}if(description.trim().length<10){setFormError('Add enough immutable instructions for an agent to act safely.');return}m.mutate()};return <div className="modal-backdrop" onMouseDown={close}><section className="create modal" role="dialog" aria-modal="true" aria-labelledby="new-task-title" onMouseDown={e=>e.stopPropagation()}><div className="modal-heading"><div><p className="eyebrow">Dispatch new work</p><h2 id="new-task-title">New task</h2></div><button className="icon-button" onClick={close} aria-label="Close task creation">×</button></div><p>Give the harness a clear outcome and immutable operating instructions.</p><div className={'eligibility '+(!online.length?'warning':'')}><b>{online.length?`${online.length} worker${online.length===1?'':'s'} currently reachable`:'No workers are currently reachable'}</b><span>{online.length?'Orchestra will choose an eligible worker when the task is leased.':'This task will remain queued until a matching worker reconnects.'}</span></div><form onSubmit={submit} noValidate><label>Task title<input ref={first} required value={title} onChange={e=>setTitle(e.target.value)} placeholder="e.g. Add the health endpoint" autoComplete="off"/></label><label>Project <small>Existing projects are suggested; project IDs remain explicit.</small><input required list="projects" value={project} onChange={e=>setProject(e.target.value)} placeholder="Choose or enter a project" autoComplete="off"/><datalist id="projects">{projects.map(p=><option value={p} key={p}/>)}</datalist></label><label>Capabilities <small>Optional, comma-separated. Only use registered capability names.</small><input value={capability} onChange={e=>setCapability(e.target.value)} placeholder="go, docker" autoComplete="off"/></label><label>Immutable instructions<textarea required value={description} onChange={e=>setDescription(e.target.value)} placeholder="Describe the outcome, constraints, and evidence required."/></label><details><summary>Additional accepted task fields</summary><textarea aria-label="Additional task fields JSON" placeholder={'{"parent":"…","inherent_priority":1,"due":"2026-07-30T12:00:00Z"}'} value={advanced} onChange={e=>setAdvanced(e.target.value)}/></details><div className="modal-actions"><button type="button" className="quiet-button" onClick={close}>Cancel</button><button disabled={m.isPending}>{m.isPending?'Creating task…':'Create task'}</button></div>{(formError||m.error)&&<span className="error" role="alert">{formError||String(m.error)}</span>}</form></section></div>}
|
||||
|
||||
|
||||
+2
-1
@@ -14,9 +14,10 @@
|
||||
/* Operator comfort pass: make live state scannable before the raw data. */
|
||||
.account{position:relative}.account-button{display:flex;align-items:center;gap:7px;padding:7px 9px;border-color:var(--line);color:var(--text-mid);background:transparent;font-size:11px;font-weight:500}.account-menu{position:absolute;top:calc(100% + 8px);right:0;z-index:10;display:grid;min-width:190px;gap:8px;padding:10px;border:1px solid var(--line-hi);border-radius:var(--r-sm);background:var(--bg-2);box-shadow:var(--shadow-soft);color:var(--text-lo);font-size:11px}.account-menu button{padding:7px 9px;text-align:left}.status-dot{display:inline-block;width:7px;height:7px;border-radius:50%;background:var(--accent-hi);box-shadow:0 0 0 3px var(--accent-dim)}
|
||||
.section-title>div{display:grid;gap:3px}.section-title p,.section-copy,.worker-note{margin:0;color:var(--text-lo);font-size:12px;line-height:1.5}.card em{display:block;overflow:hidden;margin-top:9px;color:var(--text-lo);font-size:11px;font-style:normal;line-height:1.35;text-overflow:ellipsis;white-space:nowrap}.card:hover em{color:var(--text-mid)}.eligibility{display:grid;gap:3px;padding:10px 12px;border:1px solid var(--accent-line);border-radius:var(--r-sm);background:var(--accent-dim);color:var(--text-mid);font-size:12px}.eligibility b{color:var(--accent-hi);font-size:12px}.eligibility.warning{border-color:rgba(213,160,146,.45);background:rgba(213,160,146,.09)}.eligibility.warning b{color:#d5a092}
|
||||
.board-filters{display:flex;align-items:center;gap:8px;margin:-2px 0 16px}.view-tabs{display:flex;gap:4px;margin-right:auto;padding:3px;border:1px solid var(--line);border-radius:var(--r-sm);background:var(--bg-1)}.view-tabs button{padding:6px 9px;border-color:transparent;color:var(--text-lo);background:transparent;font-size:11px;font-weight:500}.view-tabs button.selected{border-color:var(--accent-line);color:var(--accent-hi);background:var(--accent-dim)}.view-tabs span{margin-left:4px;color:var(--text-machine);font:10px var(--mono)}.board-filters input,.board-filters select{width:auto;min-width:150px;padding:8px 10px;border:1px solid var(--line-hi);border-radius:var(--r-sm);outline:none;color:var(--text-mid);background:var(--bg-1);font-size:12px}.board-filters select{cursor:pointer}.board-filters input:focus,.board-filters select:focus{border-color:var(--accent)}
|
||||
.task-banner{display:flex;align-items:center;justify-content:space-between;gap:20px;margin:-8px 0 20px;padding:13px 16px;border:1px solid var(--accent-line);border-radius:var(--r-md);background:var(--accent-dim)}.task-banner>div{display:flex;align-items:center;gap:12px}.task-banner .task-state{margin:0;color:var(--accent-hi);font-weight:600}.task-banner p{margin:0;color:var(--text-mid);font-size:13px;line-height:1.45}.task-banner>span{color:var(--text-lo);font:11px var(--mono);white-space:nowrap}.task-banner>span b{color:var(--text-machine);font-weight:500}.task-banner.state-blocked,.task-banner.state-failed{border-color:rgba(213,160,146,.42);background:rgba(213,160,146,.08)}
|
||||
.diagnosis{display:grid;gap:17px;margin:-8px 0 20px;padding:20px;border:1px solid var(--accent-line);border-radius:var(--r-md);background:var(--accent-dim)}.diagnosis.state-blocked,.diagnosis.state-failed{border-color:rgba(213,160,146,.42);background:rgba(213,160,146,.08)}.diagnosis-heading{display:flex;align-items:start;justify-content:space-between;gap:20px}.diagnosis .eyebrow{margin-bottom:6px}.diagnosis h2{margin:0 0 7px;font-size:22px;letter-spacing:-.03em}.diagnosis-heading p:not(.eyebrow){max-width:780px;margin:0;color:var(--text-mid);font-size:13px;line-height:1.5}.diagnosis .task-state{flex:none;margin:0;color:var(--accent-hi);font-weight:600}.diagnosis-evidence{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin:0}.diagnosis-evidence div{min-width:0;padding:10px 11px;border:1px solid var(--line);border-radius:var(--r-sm);background:rgba(20,17,13,.28)}.diagnosis-evidence dt{margin-bottom:5px;color:var(--text-lo);font-size:10px;letter-spacing:.07em;text-transform:uppercase}.diagnosis-evidence dd{overflow:hidden;margin:0;color:var(--text-machine);font-size:12px;line-height:1.35;text-overflow:ellipsis;white-space:nowrap}.next-action{display:flex;align-items:center;justify-content:space-between;gap:18px;padding:13px 14px;border-left:3px solid var(--accent);background:rgba(20,17,13,.35)}.next-action>div{display:grid;gap:3px}.next-action span{color:var(--accent-hi);font-size:10px;font-weight:600;letter-spacing:.1em;text-transform:uppercase}.next-action b{font-size:14px}.next-action p{margin:0;color:var(--text-mid);font-size:12px;line-height:1.45}.next-action small{flex:none;color:var(--text-lo);font:11px var(--mono)}
|
||||
.capture{margin:24px 0}.capture-head{display:flex;align-items:start;justify-content:space-between;gap:12px;margin-bottom:9px}.capture-head h2{margin:0 0 4px}.capture-head span{color:var(--text-lo);font:11px var(--mono)}.capture-filter{margin-bottom:8px;font-size:12px}.capture-more{margin:8px 0 0;color:var(--text-lo);font-size:11px}.capture-empty{display:grid;gap:5px;margin:24px 0;padding:15px;border:1px dashed var(--line-hi);border-radius:var(--r-sm);color:var(--text-mid);font-size:12px}.capture-empty b{color:var(--text-hi)}.actions-title{margin:27px 0 5px!important}.actions{margin-top:13px}.action-row,.action-form{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:12px;align-items:end;padding:13px;border:1px solid var(--line);border-radius:var(--r-sm);background:var(--bg-2)}.action-row>div,.action-form>div{display:grid;gap:4px}.action-row b,.action-form b{font-size:13px}.action-row span,.action-form span{color:var(--text-lo);font-size:11px;line-height:1.45}.action-form textarea,.action-form input{grid-column:1}.action-form button{grid-column:2;grid-row:1 / span 2}.action-form.compact input{margin:0}.notice,.session-message{padding:10px 12px;border:1px solid var(--accent-line);border-radius:var(--r-sm);color:var(--accent-hi);background:var(--accent-dim);font-size:12px;line-height:1.45}.timeline-surface{margin-top:20px}.timeline-heading{display:flex;align-items:start;justify-content:space-between;gap:16px}.timeline-heading p{margin:-8px 0 12px;color:var(--text-lo);font-size:12px}.timeline-heading>span{color:var(--text-lo);font:11px var(--mono)}.worker-note{margin:-10px 0 15px;padding:10px 12px;border-left:2px solid var(--accent-line);background:var(--bg-1)}.online .status-dot{margin-right:4px}.offline .status-dot{background:#c09280;box-shadow:0 0 0 3px rgba(192,146,128,.13)}
|
||||
.unavailable-actions{margin-top:22px;padding-top:17px;border-top:1px solid var(--line)}.unavailable-actions summary{color:var(--text-mid);font-size:13px;font-weight:600}.unavailable-actions>p{margin:9px 0 0;color:var(--text-lo);font-size:12px;line-height:1.45}.unavailable-actions .actions{opacity:.72}.unavailable-actions button:disabled{display:none}
|
||||
.approval-summary{display:flex;flex-wrap:wrap;gap:7px;margin:12px 0}.approval-summary span{padding:5px 7px;border:1px solid var(--line);border-radius:5px;color:var(--text-lo);font:10px var(--mono)}.approval-summary b{color:var(--text-machine);font-weight:500}.login-card .session-message{margin:-6px 0 4px}
|
||||
@media(max-width:760px){.account{margin-left:auto}.account-button{font-size:0}.account-button .status-dot{width:8px;height:8px}.task-banner,.task-banner>div,.diagnosis-heading,.next-action{align-items:start;flex-direction:column;gap:7px}.task-banner>span{white-space:normal}.diagnosis-evidence{grid-template-columns:repeat(2,minmax(0,1fr))}.action-row,.action-form{grid-template-columns:1fr}.action-row button,.action-form button{grid-column:auto;grid-row:auto;justify-self:start}.action-form textarea,.action-form input{grid-column:auto}.capture-head{align-items:stretch;flex-direction:column}.section-title{align-items:start;gap:12px;flex-direction:column}.section-title .machine{white-space:nowrap}.readout{display:none}}
|
||||
@media(max-width:760px){.account{margin-left:auto}.account-button{font-size:0}.account-button .status-dot{width:8px;height:8px}.task-banner,.task-banner>div,.diagnosis-heading,.next-action{align-items:start;flex-direction:column;gap:7px}.task-banner>span{white-space:normal}.diagnosis-evidence{grid-template-columns:repeat(2,minmax(0,1fr))}.board-filters{align-items:stretch;flex-wrap:wrap}.view-tabs{width:100%;margin-right:0}.board-filters input,.board-filters select{flex:1;min-width:0}.action-row,.action-form{grid-template-columns:1fr}.action-row button,.action-form button{grid-column:auto;grid-row:auto;justify-self:start}.action-form textarea,.action-form input{grid-column:auto}.capture-head{align-items:stretch;flex-direction:column}.section-title{align-items:start;gap:12px;flex-direction:column}.section-title .machine{white-space:nowrap}.readout{display:none}}
|
||||
|
||||
Reference in New Issue
Block a user