feat(web): order the shipped presets, and edit one on its own screen
The picker listed presets alphabetically by id, so the shipped set read cordis, minimal, standard — reverse order of capability. A preset may now declare `order` in its metadata; the shipped three declare 1/2/3 and read standard, minimal, cordis. A preset that declares none sorts behind those that do, then by id, so authored presets stay stable. Editing had nowhere good to live. Inside a card it was squeezed into a ~268px column; hanging off the end of the grid it was orphaned from the card it edits. It now replaces the list: a back link, what is being edited, and the form at full width. One thing on screen at a time, which is what the form's height wanted all along. Cards in different grid rows sized independently, so a short description made a short card. `grid-auto-rows: 1fr` makes every row the same height. The trust badge lost its pill when the card CSS was rewritten, and `In use` never had one; both are tags now. Icon labels moved from `title` to a drawn tooltip — the native one waits about a second, which reads as nothing happening.
This commit is contained in:
@@ -61,7 +61,12 @@ export async function scanRoot(root: PresetRoot): Promise<AgentPreset[]> {
|
||||
const metadata = await readPresetMetadata(directory)
|
||||
found.push({ id: child.name, trust: root.trust, path, ...metadata })
|
||||
}
|
||||
return found.sort((left, right) => left.id.localeCompare(right.id))
|
||||
// Declared order first so the shipped set reads by capability; everything
|
||||
// else falls back to the id, which keeps authored presets stable.
|
||||
return found.sort((left, right) => {
|
||||
const byOrder = (left.order ?? Number.POSITIVE_INFINITY) - (right.order ?? Number.POSITIVE_INFINITY)
|
||||
return byOrder === 0 ? left.id.localeCompare(right.id) : byOrder
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,6 +30,12 @@ export interface PresetMetadata {
|
||||
readonly name?: string
|
||||
/** One sentence on what this preset is for. */
|
||||
readonly description?: string
|
||||
/**
|
||||
* Position within its group; lower comes first. A preset that declares
|
||||
* none sorts after every preset that does, then by id — so the shipped set
|
||||
* can read in capability order while authored ones stay alphabetical.
|
||||
*/
|
||||
readonly order?: number
|
||||
}
|
||||
|
||||
/** A non-empty trimmed string, or undefined for anything else. */
|
||||
@@ -68,9 +74,13 @@ export async function readPresetMetadata(directory: string): Promise<PresetMetad
|
||||
const record = parsed as Record<string, unknown>
|
||||
const name = text(record.name)
|
||||
const description = text(record.description)
|
||||
const order = typeof record.order === 'number' && Number.isFinite(record.order)
|
||||
? record.order
|
||||
: undefined
|
||||
return {
|
||||
...name === undefined ? {} : { name },
|
||||
...description === undefined ? {} : { description },
|
||||
...order === undefined ? {} : { order },
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,9 +95,11 @@ export async function readPresetMetadata(directory: string): Promise<PresetMetad
|
||||
export function renderPresetMetadata(metadata: PresetMetadata): string | undefined {
|
||||
const name = text(metadata.name)
|
||||
const description = text(metadata.description)
|
||||
if (name === undefined && description === undefined) return undefined
|
||||
const { order } = metadata
|
||||
if (name === undefined && description === undefined && order === undefined) return undefined
|
||||
return yaml.dump({
|
||||
...name === undefined ? {} : { name },
|
||||
...description === undefined ? {} : { description },
|
||||
...order === undefined ? {} : { order },
|
||||
}, { lineWidth: -1 })
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ export interface AgentPreset {
|
||||
readonly name?: string
|
||||
/** One sentence on what this preset is for, when it published one. */
|
||||
readonly description?: string
|
||||
/** Declared position within its group; absent sorts after those that declare one. */
|
||||
readonly order?: number
|
||||
}
|
||||
|
||||
/** One directory scanned for preset subdirectories. */
|
||||
|
||||
@@ -9,6 +9,27 @@ const FIXTURES = join(dirname(fileURLToPath(import.meta.url)), 'fixtures')
|
||||
const SYSTEM = { path: join(FIXTURES, 'system'), trust: 'system' as const }
|
||||
const USER = { path: join(FIXTURES, 'user'), trust: 'user' as const }
|
||||
|
||||
describe('display order', () => {
|
||||
it('puts declared order first, then everything else by id', async () => {
|
||||
const root = await mkdtemp(join(tmpdir(), 'dsh-order-'))
|
||||
for (const [id, order] of [['zulu', 1], ['alpha', 2]] as const) {
|
||||
await mkdir(join(root, id), { recursive: true })
|
||||
await writeFile(join(root, id, COMPOSITION_FILE), '[]\n')
|
||||
await writeFile(join(root, id, 'preset.yml'), `order: ${String(order)}\n`)
|
||||
}
|
||||
for (const id of ['bravo', 'yankee']) {
|
||||
await mkdir(join(root, id), { recursive: true })
|
||||
await writeFile(join(root, id, COMPOSITION_FILE), '[]\n')
|
||||
}
|
||||
|
||||
const found = await scanRoot({ path: root, trust: 'system' })
|
||||
|
||||
// The shipped set reads by capability; presets that declare nothing stay
|
||||
// alphabetical behind them rather than interleaving unpredictably.
|
||||
expect(found.map(preset => preset.id)).toEqual(['zulu', 'alpha', 'bravo', 'yankee'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('preset discovery', () => {
|
||||
it('reports one preset per directory holding a composition, ordered by id', async () => {
|
||||
const found = await scanRoot(SYSTEM)
|
||||
|
||||
@@ -67,6 +67,17 @@ describe('reading display metadata', () => {
|
||||
expect(await readPresetMetadata(dir)).toEqual({ name: '极简模式' })
|
||||
})
|
||||
|
||||
it('reads a declared order', async () => {
|
||||
const dir = await presetDir('name: 标准模式\norder: 1\n')
|
||||
|
||||
expect(await readPresetMetadata(dir)).toEqual({ name: '标准模式', order: 1 })
|
||||
})
|
||||
|
||||
it('ignores an order that is not a finite number', async () => {
|
||||
expect(await readPresetMetadata(await presetDir('order: first\n'))).toEqual({})
|
||||
expect(await readPresetMetadata(await presetDir('order: .inf\n'))).toEqual({})
|
||||
})
|
||||
|
||||
it('cannot carry identity or trust', async () => {
|
||||
const dir = await presetDir('name: mine\nid: standard\ntrust: system\n')
|
||||
|
||||
|
||||
Reference in New Issue
Block a user