Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion __tests__/durable-write.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ function resolveEsbuild() {
function buildModule() {
const out = mkdtempSync(join(tmpdir(), 'news-test-'))
const shim = join(out, 'react-shim.js')
writeFileSync(shim, 'export const jsx=()=>null; export const jsxs=()=>null; export const Fragment=null; export default {}; export const useState=()=>[]; export const useEffect=()=>{}; export const useCallback=(f)=>f; export const useMemo=()=>undefined; export const useRef=()=>({current:null});')
writeFileSync(shim, 'export const jsx=()=>null; export const jsxs=()=>null; export const Fragment=null; export const createPortal=(node)=>node; export default {}; export const useState=()=>[]; export const useEffect=()=>{}; export const useCallback=(f)=>f; export const useId=()=>"test-id"; export const useMemo=()=>undefined; export const useRef=()=>({current:null});')
const bundle = join(out, 'news.mjs')
execFileSync(resolveEsbuild(), [
join(repo, 'index.jsx'),
Expand Down
57 changes: 57 additions & 0 deletions __tests__/news-regressions.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
} from '../domain.js'
import { isErrorReport } from '../report-schema.mjs'
import { EFFORT_LEVELS, defaultEffort } from '../constants.js'
import { canReorderAgentSlots, reorderAgentSlots } from '../ui/backgroundAgentOrder.js'

const HERE = dirname(fileURLToPath(import.meta.url))
const repo = join(HERE, '..')
Expand Down Expand Up @@ -443,6 +444,61 @@ test('settings writes explicit Background-agent modes and preserves legacy overr
assert.equal(manifest.storage_seeds['agent.json'].secondary_agent_mode, 'system')
})

test('background agent slots use the Settings-style picker with an inherited default row', () => {
const settings = readRepoFile(join('ui', 'SettingsTab.jsx'))
const picker = readRepoFile(join('ui', 'ModelPicker.jsx'))
const priorityList = readRepoFile(join('ui', 'BackgroundAgentList.jsx'))
const theme = readRepoFile('theme.js')
assert.ok(settings.includes("useSettingsDefault={primaryAgentMode === 'system'}"))
assert.ok(settings.includes("useSettingsDefault={secondaryAgentMode === 'system'}"))
assert.ok(settings.includes('onChange={saveAgent}'))
assert.ok(settings.includes("onSettingsDefault={() => savePrimaryMode('system')}"))
assert.ok(settings.includes('onChange={saveFallbackAgent}'))
assert.ok(settings.includes('onSettingsDefault={() => toggleFallback(false)}'))
assert.ok(!settings.includes('aria-label="News primary agent mode"'))
assert.ok(!settings.includes('aria-label="News secondary agent mode"'))
assert.ok(picker.includes('Default from settings'))
assert.ok(picker.includes('onSettingsDefault'))
assert.ok(picker.includes('aria-label={triggerLabel}'))
assert.ok(picker.includes('aria-pressed={useSettingsDefault}'))
assert.ok(picker.includes('aria-pressed={selected}'))
assert.ok(picker.includes('`${title}: ${modelName}${effortLabel ? `, ${effortLabel} effort`'))
assert.match(picker, /\{open && createPortal\([\s\S]*document\.body,\s*\)\}/)
assert.match(picker, /event\.target === event\.currentTarget\) closeSheet\(\)/)
assert.match(picker, /mobius-model-sheet__close" onClick=\{closeSheet\}/)
assert.match(picker, /closeRef\.current\?\.focus\?\.\(\)/)
assert.match(picker, /triggerRef\.current\?\.focus\?\.\(\)/)
assert.match(theme, /\.mobius-model-sheet__backdrop\s*\{[\s\S]*z-index:\s*1000/)
assert.ok(settings.includes('<BackgroundAgentList'))
assert.ok(settings.includes('onMove={reorderAgents}'))
assert.ok(settings.includes('reorderAgentSlots(slots, fromIndex, toIndex)'))
assert.ok(settings.includes('setPrimaryAgentMode(next.primaryAgentMode)'))
assert.ok(settings.includes('setSecondaryAgentMode(next.secondaryAgentMode)'))
assert.ok(settings.includes('setPrimaryAgentMode(previous.primary.mode)'))
assert.ok(settings.includes('setSecondaryAgentMode(previous.secondary.mode)'))
assert.ok(settings.includes('seq !== saveAgentSeqRef.current'))
assert.ok(priorityList.includes('mobius-agent-priority-handle'))
assert.ok(priorityList.includes('onPointerDown'))
assert.ok(priorityList.includes("event.key === 'ArrowUp'"))
assert.ok(priorityList.includes('itemLabels'))
assert.ok(priorityList.includes('aria-live="polite"'))
assert.ok(settings.includes('reorderDisabled={!canReorderAgents}'))
})

test('background agent reorder preserves concrete identities and rejects inherited slots', () => {
const primary = { mode: 'app', provider: 'claude', model: 'claude-opus', effort: 'high' }
const fallback = { mode: 'app', provider: 'codex', model: 'gpt-codex', effort: 'medium' }
const before = [primary, fallback]
const after = reorderAgentSlots(before, 1, 0)
assert.equal(canReorderAgentSlots(before), true)
assert.deepEqual(after, [fallback, primary])
assert.notEqual(after[0], fallback)

const inherited = [{ mode: 'system' }, fallback]
assert.equal(canReorderAgentSlots(inherited), false)
assert.equal(reorderAgentSlots(inherited, 0, 1), inherited)
})

test('fetch.sh resolves and retries a configured fallback agent', () => {
const sh = readRepoFile('fetch.sh')
assert.ok(sh.includes('from app.background_agents import resolve_background_agents'))
Expand All @@ -467,6 +523,7 @@ test('mechanical manifest and token fixes stay in place', () => {
assert.equal(manifest.version, pkg.version)
assert.equal(manifest.embeds_agent, true)
assert.ok(manifest.source_files.includes('ui/EffortStepper.jsx'))
assert.ok(manifest.source_files.includes('ui/BackgroundAgentList.jsx'))
assert.deepEqual(manifest.offline, { reads: true, writes: 'queued', execution: 'none' })
assert.ok(!/color:\s*#fff/.test(theme))
assert.ok(!/color:\s*var\(--bg\)/.test(theme))
Expand Down
4 changes: 3 additions & 1 deletion mobius.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "news",
"name": "News",
"version": "1.14.10",
"version": "1.14.11",
"description": "A daily AI-curated news digest you can tune by topic, schedule, and editorial brief.",
"offline_capable": true,
"embeds_agent": true,
Expand All @@ -24,6 +24,8 @@
"ui/ReportsTab.jsx",
"ui/ModelPicker.jsx",
"ui/EffortStepper.jsx",
"ui/BackgroundAgentList.jsx",
"ui/backgroundAgentOrder.js",
"ui/SettingsTab.jsx"
],
"theme_color": "#0c0f14",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@mobius-os/app-news",
"version": "1.14.10",
"version": "1.14.11",
"private": true,
"type": "module",
"scripts": {
Expand Down
81 changes: 54 additions & 27 deletions theme.js
Original file line number Diff line number Diff line change
Expand Up @@ -534,32 +534,46 @@ export const CSS = `
color: var(--muted); white-space: nowrap;
}
.nw-effort.is-disabled .nw-effort-label { opacity: 0.55; }
.nw-agent-stack { display: grid; gap: 12px; }
.nw-agent-slot {
display: grid; gap: 8px; min-width: 0; padding: 10px;
border: 1px solid var(--border); border-radius: 10px;
background: color-mix(in srgb, var(--surface) 72%, var(--bg));
}
.nw-agent-slot-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
.nw-agent-slot-title { color: var(--text); font-size: 12.5px; font-weight: 700; }
.nw-agent-mode {
display: inline-flex; gap: 2px; padding: 2px;
border: 1px solid var(--border); border-radius: 8px; background: var(--bg);
}
.nw-agent-mode-btn {
min-height: 44px; padding: 0 10px; border: none; border-radius: 6px;
background: transparent; color: var(--muted); font: inherit;
font-size: 12px; font-weight: 700; cursor: pointer;
touch-action: manipulation; user-select: none;
}
.nw-agent-mode-btn.is-active { background: var(--accent-hover, var(--accent)); color: var(--accent-fg); }
.nw-agent-inherit {
min-height: 42px; display: flex; align-items: center; padding: 9px 10px;
border: 1px dashed var(--border); border-radius: 8px;
background: var(--bg); color: var(--muted); font-size: 12.5px; line-height: 1.4;
}
@media (hover: hover) { .nw-agent-mode-btn:not(.is-active):hover { color: var(--text); } }

.mobius-agent-priority-list { display:flex; flex-direction:column; gap:6px; position:relative; }
.mobius-agent-priority-row {
position:relative; display:grid; grid-template-columns:44px minmax(0,1fr);
align-items:center; min-height:54px; padding:0; border:0; border-radius:9px;
background:transparent; user-select:none; -webkit-user-select:none;
-webkit-touch-callout:none; will-change:transform;
transition:transform .18s cubic-bezier(.22,1,.36,1), background .15s ease,
border-color .15s ease, box-shadow .15s ease, opacity .15s ease;
}
.mobius-agent-priority-list.is-committing .mobius-agent-priority-row { transition:none; }
.mobius-agent-priority-row.is-dragging { opacity:.96; }
.mobius-agent-priority-row.is-dragging .mobius-model-trigger,
.mobius-agent-priority-row.is-drop-target .mobius-model-trigger {
border-color:color-mix(in srgb,var(--accent) 62%,var(--border));
background:color-mix(in srgb,var(--accent) 7%,var(--surface));
box-shadow:0 4px 8px rgb(0 0 0 / 18%);
}
.mobius-agent-priority-handle {
align-self:stretch; min-width:44px; min-height:44px; display:grid; place-items:center;
border:0; border-radius:7px; padding:0; color:var(--muted); background:transparent;
font:inherit; cursor:grab; touch-action:none; -webkit-tap-highlight-color:transparent;
}
.mobius-agent-priority-handle:active,
.mobius-agent-priority-row.is-dragging .mobius-agent-priority-handle {
cursor:grabbing; color:var(--accent);
background:color-mix(in srgb,var(--accent) 10%,transparent);
}
.mobius-agent-priority-handle:focus-visible { outline:2px solid var(--accent); outline-offset:1px; }
.mobius-agent-priority-handle svg { display:block; }
.mobius-agent-priority-handle:disabled { cursor:not-allowed; opacity:.45; }
.mobius-agent-priority-body { min-width:0; }
.mobius-agent-priority-help { margin:0 0 2px; color:var(--muted); font-size:12px; line-height:1.4; }
.mobius-agent-priority-status {
position:absolute; width:1px; height:1px; padding:0; margin:-1px;
overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; border:0;
}
@media (hover:hover) and (pointer:fine) {
.mobius-agent-priority-handle:not(:disabled):hover { color:var(--text); background:var(--surface2); }
}
@media (prefers-reduced-motion:reduce) { .mobius-agent-priority-row { transition:none; } }
/* Production Settings model trigger + responsive picker vocabulary. */
.mobius-model-trigger {
display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
Expand All @@ -586,7 +600,20 @@ export const CSS = `
background: color-mix(in srgb, var(--accent) 12%, var(--surface));
border: 1px solid color-mix(in srgb, var(--accent) 24%, var(--border));
}
.mobius-model-trigger__caret { color: var(--muted); font-size: 11px; flex-shrink: 0; }
.mobius-model-trigger__effort-visual {
position: relative; flex-shrink: 0; display: inline-flex; align-items: center;
justify-content: space-between; gap: 5px; min-width: 68px; padding: 7px 3px;
}
.mobius-model-trigger__effort-visual::before {
content: ''; position: absolute; left: 6px; right: 6px; top: 50%; height: 1px;
background: var(--border); transform: translateY(-50%);
}
.mobius-model-trigger__effort-dot {
position: relative; z-index: 1; width: 6px; height: 6px; border-radius: 50%;
border: 1px solid var(--border); background: var(--surface);
}
.mobius-model-trigger__effort-dot.is-filled { border-color: var(--accent); background: var(--accent); }
.mobius-model-trigger__effort-dot.is-active { transform: scale(1.35); box-shadow: 0 0 0 2px var(--accent-dim); }
.mobius-model-sheet__backdrop {
position: absolute; inset: 0; z-index: 1000; display: flex;
align-items: flex-end; justify-content: center; box-sizing: border-box;
Expand Down
Loading
Loading