portal-mojo 0.2.21 → 0.2.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/docs/admin-assistant.md +75 -23
  2. package/docs/admin-dns-domains.md +96 -90
  3. package/docs/admin-dns-records.md +1 -1
  4. package/docs/admin-dns-registrar.md +29 -4
  5. package/docs/admin-dns.md +2 -3
  6. package/docs/admin-modals.md +21 -0
  7. package/docs/menus-and-access.md +24 -0
  8. package/package.json +1 -1
  9. package/src/admin/assistant/AssistantFeed.tsx +7 -4
  10. package/src/admin/assistant/AssistantSetupPage.tsx +141 -64
  11. package/src/admin/assistant/api.ts +9 -7
  12. package/src/admin/assistant/pages.tsx +125 -109
  13. package/src/admin/assistant/setup.ts +1 -1
  14. package/src/admin/assistant/skill-lifecycle.ts +5 -4
  15. package/src/admin/core/index.ts +10 -2
  16. package/src/admin/dns/DnsRecordsPanel.tsx +45 -9
  17. package/src/admin/dns/DomainAssignment.tsx +55 -0
  18. package/src/admin/dns/DomainDetail.tsx +55 -38
  19. package/src/admin/dns/DomainOnboardingContent.tsx +9 -4
  20. package/src/admin/dns/DomainPurchaseDetail.tsx +42 -7
  21. package/src/admin/dns/DomainPurchaseWizard.tsx +102 -72
  22. package/src/admin/dns/DomainPurchasesPage.tsx +1 -1
  23. package/src/admin/dns/DomainRegisterExisting.tsx +21 -6
  24. package/src/admin/dns/DomainRegistrarSettings.tsx +28 -48
  25. package/src/admin/dns/DomainRegistrationPanel.tsx +127 -0
  26. package/src/admin/dns/DomainsPage.tsx +25 -24
  27. package/src/admin/dns/ProviderCredentialsPage.tsx +32 -18
  28. package/src/admin/dns/RegistrantContactPage.tsx +73 -27
  29. package/src/admin/dns/api.ts +26 -25
  30. package/src/admin/dns/domain-authority.ts +11 -0
  31. package/src/admin/dns/index.ts +2 -2
  32. package/src/admin/dns/purchase-data.ts +5 -3
  33. package/src/admin/dns/registrar-api.ts +57 -11
  34. package/src/admin/domains/infrastructure.ts +8 -8
  35. package/src/admin/domains/operations.ts +6 -6
  36. package/src/admin/edge/VhostsPage.tsx +6 -1
  37. package/src/admin/jobs/JobDashboardPage.tsx +14 -4
  38. package/src/client/mock.ts +14 -8
  39. package/src/styles/components/admin-assistant.css +83 -20
  40. package/src/styles/components/admin-domains.css +67 -11
  41. package/src/styles/components/admin-edge.css +1 -0
  42. package/src/styles/components/admin-jobs.css +1 -0
  43. package/src/ui/SidebarNav.tsx +10 -1
  44. package/src/ui/menu-registry.ts +2 -0
@@ -6779,6 +6779,8 @@ let domainRegistrarSaveFault: 'warning' | 'ambiguous' | null = null;
6779
6779
  let dnsWriteFault: 'reject' | 'ambiguous' | 'reconcile' | null = null;
6780
6780
  let dnsFailNextRead = false;
6781
6781
  const mockRegistrantScopeState = new Map<string, 'saved' | 'cleared'>();
6782
+ const mockRegistrantConfigured = (group: number) => mockRegistrantScopeState.get(group > 0 ? `group:${group}` : 'house') === 'saved'
6783
+ || dnsRegistrarMode !== 'contact-missing' && (group === 0 || group % 2 === 1);
6782
6784
 
6783
6785
  export type MockUploadMode = 'raw-put' | 'raw-put-unprefixed' | 'config-put' | 'config-put-bearer' | 'config-post';
6784
6786
  export type MockUploadFault = 'initiate-ambiguous' | 'transfer-ambiguous' | 'transfer-failed' | 'transfer-expired' | 'complete-reject' | 'complete-ambiguous' | 'complete-body-malformed';
@@ -6903,7 +6905,7 @@ export function setMockDnsAcmeMode(value: 'production' | 'staging' | 'unconfigur
6903
6905
  export function setMockDnsRegistrarMode(value: 'ready' | 'disabled' | 'contact-missing'): void { dnsRegistrarMode = value; }
6904
6906
 
6905
6907
  /** Verifier-only one-shot result after a confirmation token is consumed. */
6906
- export function armMockRegistrarPurchaseFault(value: 'failed' | 'ambiguous'): void { dnsRegistrarFault = value; }
6908
+ export function armMockRegistrarPurchaseFault(value: 'failed' | 'ambiguous' | null): void { dnsRegistrarFault = value; }
6907
6909
  export function armMockDomainRegistrarSaveFault(value: 'warning' | 'ambiguous'): void { domainRegistrarSaveFault = value; }
6908
6910
 
6909
6911
  /** Verifier-only one-shot DNS transport outcomes; all state still lives in db.dnsRecords. */
@@ -9767,7 +9769,7 @@ async function mockFetchInner(path: string, opts: MockFetchOpts): Promise<unknow
9767
9769
  const caller = userFromBearer(opts.headers);
9768
9770
  if (!caller) return permissionDenied(401);
9769
9771
  const assistantGate = hasGlobalPermission(caller, ['view_admin', 'assistant']);
9770
- const canInspect = (row: MockAssistantConversation) => row.user === caller.id || assistantGate;
9772
+ const canInspect = (row: MockAssistantConversation) => row.user === caller.id || hasGlobalPermission(caller, ['view_admin']);
9771
9773
  const canDelete = (row: MockAssistantConversation) => row.user === caller.id || hasGlobalPermission(caller, ['view_admin']);
9772
9774
  const assistantUser = (userId: number) => { const row = db.users.find((user) => user.id === userId); return row ? { id: row.id, display_name: row.display_name } : { id: userId, display_name: `User #${userId}` }; };
9773
9775
  const conversationWire = (row: MockAssistantConversation, detail = false) => ({ id: row.id, title: row.title, metadata: { ...row.metadata }, created: row.created, modified: row.modified, user: assistantUser(row.user), ...(detail ? { messages: row.messages.map((message) => ({ ...message, tool_calls: [...message.tool_calls], blocks: message.blocks == null ? null : structuredClone(message.blocks), usage: { ...message.usage } })) } : {}) });
@@ -9826,14 +9828,17 @@ async function mockFetchInner(path: string, opts: MockFetchOpts): Promise<unknow
9826
9828
  const id = conversationMatch[1] ? Number(conversationMatch[1]) : null;
9827
9829
  if (id != null) { const row = db.assistantConversations.find((candidate) => candidate.id === id); if (!row || !canInspect(row)) return { status: false, error: 'Conversation not found', error_code: 404 }; if (method === 'DELETE') { if (!canDelete(row)) return permissionDenied(); db.assistantConversations = db.assistantConversations.filter((candidate) => candidate.id !== id); return { status: 'deleted' }; } if (method !== 'GET') return { status: false, error: 'Method not allowed', error_code: 405 }; return { status: true, data: conversationWire(row, opts.params?.graph === 'detail'), graph: String(opts.params?.graph ?? 'default') }; }
9828
9830
  if (method !== 'GET') return { status: false, error: 'Method not allowed', error_code: 405 };
9829
- const visible = db.assistantConversations.filter(canInspect).sort((a, b) => b.modified - a.modified); const start = Math.max(0, Number(opts.params?.start ?? 0)); const size = Math.min(100, Math.max(1, Number(opts.params?.size ?? 100))); return { status: true, data: visible.slice(start, start + size).map((row) => conversationWire(row)), count: visible.length, start, size, graph: 'default' };
9831
+ const params = { ...opts.params, start: Math.max(0, Number(opts.params?.start ?? 0)), size: Math.min(100, Math.max(1, Number(opts.params?.size ?? 100))) };
9832
+ return { ...listRows(db.assistantConversations.filter(canInspect).map(row => conversationWire(row)), params, row => row.title, '-modified'), graph: 'default' };
9830
9833
  }
9831
9834
  const skillMatch = path.match(/^\/api\/assistant\/skill(?:\/(\d+))?$/);
9832
9835
  if (skillMatch) {
9833
9836
  if (!assistantGate) return permissionDenied(); const id = skillMatch[1] ? Number(skillMatch[1]) : null;
9834
9837
  const skillWire = (row: MockAssistantSkill, detail = false) => ({ id: row.id, tier: row.tier, name: row.name, description: row.description, auto_execute: row.auto_execute, is_active: row.is_active, created: row.created, modified: row.modified, user: row.user == null ? null : assistantUser(row.user), ...(detail ? { triggers: [...row.triggers], steps: structuredClone(row.steps), metadata: { ...row.metadata } } : {}) });
9835
9838
  if (id != null) { const row = db.assistantSkills.find((candidate) => candidate.id === id); if (!row) return { status: false, error: 'Skill not found', error_code: 404 }; if (method === 'DELETE') { db.assistantSkills = db.assistantSkills.filter((candidate) => candidate.id !== id); return { status: 'deleted' }; } if (method === 'POST') { if (!hasGlobalPermission(caller, ['view_admin'])) return permissionDenied(); if (typeof opts.body?.is_active !== 'boolean') return { status: false, error: 'is_active is required', error_code: 400 }; row.is_active = opts.body.is_active; row.modified = Math.floor(Date.now() / 1000); } else if (method !== 'GET') return { status: false, error: 'Method not allowed', error_code: 405 }; return { status: true, data: skillWire(row, opts.params?.graph === 'detail'), graph: String(opts.params?.graph ?? 'default') }; }
9836
- if (method !== 'GET') return { status: false, error: 'Method not allowed', error_code: 405 }; const rows = [...db.assistantSkills].sort((a, b) => a.name.localeCompare(b.name)); return { status: true, data: rows.map((row) => skillWire(row)), count: rows.length, start: 0, size: rows.length, graph: 'default' };
9839
+ if (method !== 'GET') return { status: false, error: 'Method not allowed', error_code: 405 };
9840
+ const params = { ...opts.params, start: Math.max(0, Number(opts.params?.start ?? 0)), size: Math.min(100, Math.max(1, Number(opts.params?.size ?? 100))) };
9841
+ return { ...listRows(db.assistantSkills.map(row => skillWire(row)), params, row => row.name, 'name'), graph: 'default' };
9837
9842
  }
9838
9843
  const memoryMatch = path.match(/^\/api\/assistant\/memory\/(global|user|group)(?:\/([^/]+))?$/);
9839
9844
  if (memoryMatch) {
@@ -9846,7 +9851,7 @@ async function mockFetchInner(path: string, opts: MockFetchOpts): Promise<unknow
9846
9851
  }
9847
9852
  const store: Record<string, unknown> = tier === 'global' ? db.assistantMemory.global : tier === 'user' ? (db.assistantMemory.users.get(caller.id) ?? {}) : (db.assistantMemory.groups.get(groupId!) ?? {});
9848
9853
  if (method === 'GET' && entryKey == null) return { status: true, data: { ...store } };
9849
- if (method === 'POST' && entryKey == null) { const key = typeof opts.body?.key === 'string' ? opts.body.key : ''; const value = typeof opts.body?.value === 'string' ? opts.body.value : ''; if (!/^[a-z0-9:_-]{1,64}$/.test(key) || value.length < 1 || value.length > 500) return { status: false, error: 'Invalid memory key or value', error_code: 400 }; store[key] = value; if (tier === 'user') db.assistantMemory.users.set(caller.id, store); if (tier === 'group') db.assistantMemory.groups.set(groupId!, store); return { status: true, data: { key, value } }; }
9854
+ if (method === 'POST' && entryKey == null) { const key = typeof opts.body?.key === 'string' ? opts.body.key : ''; const value = typeof opts.body?.value === 'string' ? opts.body.value : ''; if (!/^[a-z0-9][a-z0-9:_-]{0,63}$/.test(key) || value.length < 1 || value.length > 500) return { status: false, error: 'Invalid memory key or value', error_code: 400 }; store[key] = value; if (tier === 'user') db.assistantMemory.users.set(caller.id, store); if (tier === 'group') db.assistantMemory.groups.set(groupId!, store); return { status: true, data: { key, value } }; }
9850
9855
  if (method === 'DELETE' && entryKey != null) { delete store[entryKey]; return { status: true, data: { deleted: entryKey } }; }
9851
9856
  return { status: false, error: 'Method not allowed', error_code: 405 };
9852
9857
  }
@@ -10339,7 +10344,7 @@ async function mockFetchInner(path: string, opts: MockFetchOpts): Promise<unknow
10339
10344
  status: true,
10340
10345
  data: {
10341
10346
  purchase_enabled: dnsRegistrarMode !== 'disabled',
10342
- registrant_contact_configured: dnsRegistrarMode !== 'contact-missing' && (groupId === 0 || groupId % 2 === 1),
10347
+ registrant_contact_configured: mockRegistrantConfigured(groupId),
10343
10348
  max_domain_price: '100.00', currency: 'USD', quote_ttl_minutes: 15,
10344
10349
  allowed_record_types: ['A', 'AAAA', 'CAA', 'CNAME', 'MX', 'NS', 'SRV', 'TXT'],
10345
10350
  search_batch_limit: 20, suggestions_enabled: true,
@@ -10740,7 +10745,7 @@ async function mockFetchInner(path: string, opts: MockFetchOpts): Promise<unknow
10740
10745
  const group = db.groups.find((candidate) => candidate.id === groupId && candidate.is_active);
10741
10746
  if (!group || !body.domain) return missingParams('group', 'domain');
10742
10747
  if (dnsRegistrarMode === 'disabled') return { status: false, error: 'Domain purchasing is disabled on this deployment (DNSMAN_PURCHASE_ENABLED is off).', error_code: 400 };
10743
- if (dnsRegistrarMode === 'contact-missing' || groupId % 2 === 0) return { status: false, error: 'No registrant contact is configured', error_code: 400 };
10748
+ if (!mockRegistrantConfigured(groupId)) return { status: false, error: 'No registrant contact is configured', error_code: 400 };
10744
10749
  const answer = resultRow(String(body.domain));
10745
10750
  if (answer.available !== true || !answer.tld_supported) return { status: false, error: 'The domain cannot be quoted', error_code: 400 };
10746
10751
  const years = Number(body.years ?? 1);
@@ -10841,7 +10846,7 @@ async function mockFetchInner(path: string, opts: MockFetchOpts): Promise<unknow
10841
10846
  const direct = state === 'saved' || (state == null && groupId === 1);
10842
10847
  const settingsFile = state == null && groupId === 0;
10843
10848
  const contact = direct || settingsFile ? canonical : null;
10844
- return { status: true, data: { scope: groupId > 0 ? 'group' : 'global', group: groupId || null, contact, source: direct ? 'database' : settingsFile ? 'settings_file' : 'none', inherited: groupId > 0 && !contact && groupId % 2 === 0, effective_configured: dnsRegistrarMode !== 'contact-missing' && (Boolean(contact) || groupId % 2 === 1 || groupId === 0), problems: [] } };
10849
+ return { status: true, data: { scope: groupId > 0 ? 'group' : 'global', group: groupId || null, contact, source: direct ? 'database' : settingsFile ? 'settings_file' : 'none', inherited: groupId > 0 && !contact && groupId % 2 === 0, effective_configured: mockRegistrantConfigured(groupId), problems: [] } };
10845
10850
  }
10846
10851
 
10847
10852
  if (path === '/api/dnsman/whois' || path === '/api/dnsman/whois/privacy') {
@@ -10852,6 +10857,7 @@ async function mockFetchInner(path: string, opts: MockFetchOpts): Promise<unknow
10852
10857
  if (!domain) return { status: false, error: 'Domain not found', error_code: 404 };
10853
10858
  if (domain.provider !== 'route53') return { status: false, error: 'Registrar operations are not available for this provider', error_code: 400 };
10854
10859
  if (domain.group == null ? !caller.is_superuser : !dnsMemberCan(caller, domain.group, true)) return permissionDenied();
10860
+ if (domain.registered_on == null) return { status: false, error: 'This hosted zone has no registration in the Route 53 Domains account. Check its registrar.', error_code: 400 };
10855
10861
  if (path.endsWith('/privacy')) {
10856
10862
  domain.privacy = Boolean(input.enabled); domain.modified = Math.floor(Date.now() / 1000);
10857
10863
  return { status: true, data: { name: domain.name, privacy: domain.privacy, operation_id: `op-privacy-${domain.id}` } };
@@ -26,25 +26,88 @@
26
26
  .assistant-inspect > header, .assistant-skill-detail > header { display: flex; justify-content: space-between; gap: 12px; }.assistant-inspect > .record-feed { flex: 1; min-height: 0; }
27
27
  .assistant-memory-controls { display: grid; gap: 12px; margin-bottom: 12px; }.assistant-memory-form { display: grid; grid-template-columns: minmax(180px, .6fr) minmax(260px, 1fr) auto; gap: 10px; align-items: end; }
28
28
  @media (max-width: 760px) { .assistant-memory-form { grid-template-columns: 1fr; } }
29
- .assistant-setup { display: grid; gap: 16px; max-width: 1100px; min-width: 0; margin: 0 auto; width: 100%; }
30
- .assistant-setup h1, .assistant-setup h2, .assistant-setup h3 { margin: 0 0 10px; }
31
- .assistant-setup p { overflow-wrap: anywhere; }
32
- .assistant-setup-header, .assistant-setup-switch { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 14px; }
33
- .assistant-setup-switch { padding-top: 20px; margin-top: 18px; border-top: 1px solid var(--line); }
34
- .assistant-setup-switch > span { flex: 1; min-width: 200px; }
35
- .assistant-setup-facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 14px; margin: 18px 0; }
29
+ .assistant-setup { display: grid; min-width: 0; margin: 0 auto; width: 100%; }
30
+ .assistant-setup h1 { font-size: clamp(24px, 3vw, 32px); font-weight: 700; line-height: 1.2; margin: 0; }
31
+ .assistant-setup-header { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 14px; }
32
+ .assistant-setup-facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 14px; margin: 18px 0; }
36
33
  .assistant-setup-facts dt { color: var(--mute); font-size: 12px; }.assistant-setup-facts dd { margin: 5px 0 0; overflow-wrap: anywhere; }
37
- .assistant-setup-field { display: grid; gap: 7px; margin: 16px 0; max-width: 650px; }.assistant-setup-field input { width: 100%; min-width: 0; }
38
- .assistant-setup-key-status { padding-top: 10px; }.assistant-setup-warning { border: 1px solid var(--warn); border-radius: 8px; padding: 12px; }
39
- .assistant-setup-advanced { min-width: 0; }.assistant-setup-advanced > summary { padding: 17px; cursor: pointer; font-weight: 600; }.assistant-setup-advanced[open] > summary { border-bottom: 1px solid var(--line); }
40
- .assistant-setup .assistant-table-scroll { max-width: 100%; margin: 14px 0; }.assistant-setup table { border-collapse: collapse; width: 100%; }.assistant-setup th, .assistant-setup td { padding: 9px; text-align: left; vertical-align: top; border-bottom: 1px solid var(--line); }.assistant-setup caption { text-align: left; color: var(--mute); margin-bottom: 8px; }
41
- .assistant-setup button { white-space: normal; }.assistant-setup-confirm { border-color: var(--warn); }
42
- @media (max-width: 600px) { .assistant-setup-header > button { width: 100%; }.assistant-setup-facts { grid-template-columns: 1fr; }.assistant-setup .assistant-action-row { align-items: stretch; flex-direction: column; } }
43
- .assistant-setup h1 { font-size: clamp(24px, 3vw, 32px); font-weight: 700; line-height: 1.2; }
44
- .assistant-setup h2 { font-size: 21px; font-weight: 650; line-height: 1.3; }.assistant-setup h3 { font-size: 16px; font-weight: 650; margin-top: 22px; }
34
+ .assistant-setup-field > span { display: flex; align-items: center; gap: 6px; }
35
+ .assistant-setup-field { display: grid; gap: 8px; margin: 20px 0; }.assistant-setup-field input, .assistant-setup-field select { width: 100%; min-width: 0; }
36
+ /* Setup is a compact overview. Task dialogs own deeper configuration. */
37
+ .assistant-setup { max-width: 960px; gap: 20px; }
38
+ .assistant-setup-header p { margin: 7px 0 0; }
39
+ .assistant-setup .assistant-setup-overview { padding: 24px; }
40
+ .assistant-setup-status, .assistant-setup-status > div { display: flex; align-items: center; gap: 10px; }
41
+ .assistant-setup-status { justify-content: space-between; flex-wrap: wrap; gap: 16px; padding-bottom: 22px; }
42
+ .assistant-setup-status h2 { font-weight: 600; margin: 0; font-size: 20px; }
43
+ .assistant-setup-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--mute); flex: 0 0 auto; }
44
+ .assistant-setup-dot.is-ready { background: var(--ok); }.assistant-setup-dot.is-stopped { background: var(--warn); }
45
+ .assistant-setup-row { display: flex; align-items: center; gap: 18px; padding: 20px 0; border-top: 1px solid var(--line); min-width: 0; }
46
+ .assistant-setup-row > div { flex: 1; min-width: 0; }.assistant-setup-row > .btn { flex-shrink: 0; }
47
+ .assistant-setup-row h3 { font-weight: 600; font-size: 15px; margin: 0; display: flex; align-items: center; gap: 6px; }
48
+ .assistant-setup-row p { color: var(--mute); margin: 5px 0; font-size: 14px; }
49
+ .assistant-setup-row code { font-size: 12px; overflow-wrap: anywhere; }
50
+ .assistant-setup-tools { padding: 0 24px; }.assistant-setup-tools > .assistant-setup-row:first-child { border-top: 0; }
51
+ .assistant-setup-row > i { color: var(--accent); font-size: 19px; flex-shrink: 0; }
52
+ .assistant-setup-help { display: inline-flex; vertical-align: middle; justify-content: center; align-items: center; border: 0; border-radius: 50%; width: 28px; height: 28px; padding: 0; color: var(--mute); background: transparent; cursor: pointer; flex: 0 0 auto; }
53
+ .assistant-setup-help:hover { color: var(--accent); background: var(--surface2); }.assistant-setup-help:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
54
+ .assistant-setup-help-content { width: min(340px, calc(100vw - 32px)); max-height: min(500px, 75dvh); overflow: auto; padding: 16px; font-size: 14px; line-height: 1.5; overflow-wrap: anywhere; }
55
+ .assistant-setup-help-content p { margin: 0 0 10px; }.assistant-setup-help-content p:last-child { margin-bottom: 0; }
56
+ .assistant-setup-help-content .assistant-setup-facts { grid-template-columns: 1fr; margin: 0; }
57
+ .assistant-setup-dialog { margin: auto; width: min(680px, calc(100vw - 32px)); max-width: none; max-height: calc(100dvh - 32px); padding: 0; border: 1px solid var(--line); border-radius: 16px; background: var(--surface); color: var(--ink); box-shadow: 0 24px 80px #0004; overflow: hidden; }
58
+ .assistant-setup-dialog[open] { display: flex; flex-direction: column; }.assistant-setup-dialog::backdrop { background: #0007; }
59
+ .assistant-setup-dialog > header { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 22px 24px 18px; border-bottom: 1px solid var(--line); flex-shrink: 0; }
60
+ .assistant-setup-dialog h2 { margin: 0; font-size: 22px; line-height: 1.3; font-weight: 650; }.assistant-setup-dialog h3 { margin: 24px 0 10px; font-size: 16px; font-weight: 650; }.assistant-setup-dialog-content > h3:first-child { margin-top: 0; }
61
+ .assistant-setup-dialog-content { overflow: auto; padding: 24px; min-height: 0; overscroll-behavior: contain; }
62
+ .assistant-setup-dialog-content > :first-child { margin-top: 0; }.assistant-setup-dialog-content > :last-child { margin-bottom: 0; }
63
+ .assistant-setup-dialog p { margin: 0 0 12px; line-height: 1.5; overflow-wrap: anywhere; }.assistant-setup-dialog code { overflow-wrap: anywhere; }
64
+ .assistant-setup-dialog > footer { display: flex; justify-content: flex-end; padding: 16px 24px; border-top: 1px solid var(--line); flex-shrink: 0; }
65
+ .assistant-setup-dialog .assistant-action-row { flex-wrap: wrap; align-items: center; gap: 10px; }
66
+ .assistant-setup-dialog .assistant-setup-row:first-child { border-top: 0; padding-top: 0; }
67
+ .assistant-setup-dialog .assistant-setup-row h3 { margin: 0; }.assistant-setup-dialog .assistant-setup-field { max-width: none; }
68
+ .assistant-setup-dialog .assistant-setup-key-status { padding-top: 6px; }.assistant-setup-key-status p { margin: 6px 0; }
69
+ .assistant-setup-current { display: grid; gap: 8px; background: var(--surface2); padding: 14px; border-radius: 8px; }.assistant-setup-current > span { font-size: 12px; color: var(--mute); }
70
+ .assistant-setup-catalogue { margin-top: 18px; display: flex; gap: 6px; align-items: center; font-size: 13px; }
71
+ .assistant-setup-danger-row { border-top: 1px solid var(--line); margin-top: 24px; padding-top: 18px; }
72
+ .assistant-setup-warning { border: 1px solid var(--warn); border-radius: 8px; padding: 12px; font-size: 14px; line-height: 1.5; }
73
+ .assistant-setup-notice { padding: 12px; background: var(--surface2); border-radius: 8px; font-size: 14px; }
74
+ .assistant-setup-text-button { background: none; color: var(--accent); border: 0; padding: 0; cursor: pointer; font: inherit; text-decoration: underline; }
75
+ .assistant-setup-dialog .assistant-table-scroll { overflow-x: auto; max-width: 100%; margin: 14px 0; }.assistant-setup-dialog table { border-collapse: collapse; width: 100%; font-size: 13px; }.assistant-setup-dialog th, .assistant-setup-dialog td { padding: 10px; text-align: left; border-bottom: 1px solid var(--line); }.assistant-setup-dialog caption { text-align: left; color: var(--mute); margin-bottom: 8px; }
76
+ .assistant-setup-dialog-confirm { width: min(460px, calc(100vw - 32px)); }
77
+ @media (max-width: 600px) {
78
+ .assistant-setup { gap: 16px; }.assistant-setup .assistant-setup-overview { padding: 18px; }.assistant-setup-tools { padding: 0 18px; }
79
+ .assistant-setup-row { flex-wrap: wrap; gap: 12px; }.assistant-setup-row > div { flex-basis: calc(100% - 40px); }.assistant-setup-row > .btn { margin-left: auto; }
80
+ .assistant-setup-status > .btn { width: 100%; }.assistant-setup-dialog > header { padding: 18px; }.assistant-setup-dialog-content { padding: 18px; }.assistant-setup-dialog > footer { padding: 14px 18px; }
81
+ .assistant-setup-dialog .assistant-action-row { flex-direction: column; align-items: stretch; }.assistant-setup-dialog .assistant-setup-facts { grid-template-columns: 1fr; }.assistant-setup-dialog button { white-space: normal; }
82
+ }
83
+ .assistant-setup-area { display: grid; gap: 20px; }
84
+ .assistant-setup-mcp h2 { margin: 0 0 8px; font-size: 20px; font-weight: 650; }.assistant-setup-mcp > h3 { margin: 24px 0 12px; font-size: 16px; font-weight: 600; }
85
+ .assistant-setup-mcp > p { margin: 0 0 16px; }.assistant-setup-mcp .assistant-setup-field { max-width: 680px; }
86
+ .assistant-setup-mcp .assistant-action-row { gap: 12px; }.assistant-setup-mcp .assistant-setup-row:first-of-type { margin-top: 22px; }
87
+ @media (max-width: 600px) { .assistant-setup-mcp.panel-pad { padding: 18px; }.assistant-setup-mcp .assistant-action-row { flex-direction: column; align-items: stretch; } }
45
88
 
46
- .assistant-setup-model { min-width: 0; margin: 20px 0; padding: 0 0 20px; border-bottom: 1px solid var(--line); }
47
- .assistant-setup-field select { width: 100%; min-width: 0; }
48
- .assistant-setup-catalogue { margin-top: 16px; padding: 12px; border-radius: 8px; background: var(--surface2); overflow-wrap: anywhere; }
49
- .assistant-setup-model code { overflow-wrap: anywhere; }
50
- @media (max-width: 600px) { .assistant-setup-model .assistant-setup-field { max-width: 100%; } }
89
+ .assistant-setup-address { display: flex; align-items: center; gap: 10px; }.assistant-setup-address input { flex: 1; }.assistant-setup-address button { flex-shrink: 0; }
90
+ @media (max-width: 600px) { .assistant-setup-address { flex-direction: column; align-items: stretch; } }
91
+
92
+ /* Assistant management pages: one task per page, focused record dialogs. */
93
+ .assistant-admin-page { width: 100%; min-width: 0; display: grid; gap: 20px; max-width: 1120px; margin: 0 auto; }
94
+ .assistant-page-heading { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
95
+ .assistant-page-heading .eyebrow { display: block; margin-bottom: 8px; color: var(--mute); font-size: 11px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; }
96
+ .assistant-page-heading h1 { margin: 0; font-size: 28px; line-height: 1.25; font-weight: 700; display: flex; align-items: center; gap: 10px; }
97
+ .assistant-page-actions { display: flex; gap: 8px; align-items: center; flex-shrink: 0; }
98
+ .assistant-page-help { display: inline-flex; align-items: center; justify-content: center; vertical-align: middle; width: 28px; height: 28px; padding: 0; border: 0; background: transparent; color: var(--mute); cursor: pointer; border-radius: 6px; font-size: 15px; flex-shrink: 0; }
99
+ .assistant-page-help:hover { color: var(--ink); background: var(--surface2); }.assistant-page-help:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
100
+ .assistant-page-help-content { width: min(340px, calc(100vw - 24px)); padding: 16px; font-size: 13px; line-height: 1.6; overflow-wrap: anywhere; }.assistant-page-help-content p { margin: 0 0 10px; }.assistant-page-help-content p:last-child { margin: 0; }
101
+ .assistant-page-search { position: relative; max-width: 380px; }.assistant-page-search > i { position: absolute; left: 12px; top: 12px; color: var(--mute); }.assistant-page-search .input { padding-left: 36px; width: 100%; }
102
+ .assistant-page-list { overflow: hidden; min-width: 0; }.assistant-page-list table { width: 100%; border-collapse: collapse; table-layout: fixed; }.assistant-page-list th, .assistant-page-list td { padding: 16px 20px; border-bottom: 1px solid var(--line); text-align: left; vertical-align: middle; overflow-wrap: anywhere; }.assistant-page-list th { background: var(--surface2); color: var(--mute); font-size: 11px; text-transform: uppercase; letter-spacing: .07em; font-weight: 700; }.assistant-page-list th:first-child { width: 55%; }.assistant-page-list tr:last-child td { border-bottom: 0; }.assistant-page-list td small { display: block; color: var(--mute); font-size: 12px; margin-top: 5px; line-height: 1.5; }.assistant-page-list .link-button { text-align: left; font-weight: 600; overflow-wrap: anywhere; }.assistant-page-list td .badge { margin-left: 6px; }
103
+ .assistant-page-empty { padding: 32px 24px; margin: 0; text-align: center; color: var(--mute); }.assistant-page-empty p { margin: 0 0 8px; }.assistant-page-empty .btn { margin-top: 12px; }
104
+ .assistant-page-pager { display: flex; justify-content: flex-end; align-items: center; gap: 8px; padding: 12px 16px; border-top: 1px solid var(--line); }.assistant-page-pager span { margin-right: auto; color: var(--mute); font-size: 12px; }
105
+ .assistant-page-dialog { width: min(640px, calc(100vw - 32px)); max-width: none; max-height: calc(100dvh - 40px); padding: 0; margin: auto; border: 1px solid var(--line); border-radius: 14px; background: var(--surface); color: var(--ink); box-shadow: var(--shadow-pop); overflow: hidden; }
106
+ .assistant-page-dialog[open] { display: flex; flex-direction: column; }.assistant-page-dialog::backdrop { background: rgb(0 0 0 / .55); backdrop-filter: blur(3px); }.assistant-page-dialog > header { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 20px 24px; border-bottom: 1px solid var(--line); flex: none; }.assistant-page-dialog h2 { margin: 0; font-size: 20px; font-weight: 700; line-height: 1.4; overflow-wrap: anywhere; }.assistant-page-dialog-body { min-height: 0; overflow-y: auto; padding: 24px; }.assistant-page-dialog-body > .field { margin-top: 20px; }.assistant-page-dialog-body > p:first-child { margin-top: 0; }.assistant-page-dialog > footer { display: flex; justify-content: flex-end; gap: 10px; flex-wrap: wrap; border-top: 1px solid var(--line); padding: 16px 24px; flex: none; }.assistant-page-meta { color: var(--mute); margin: 0 0 18px; font-size: 12px; line-height: 1.6; }.assistant-page-readonly { padding: 10px 12px; background: var(--surface2); color: var(--mute); border-radius: 8px; font-size: 12px; line-height: 1.6; margin: 0 0 16px; }
107
+ .assistant-page-dialog-conversation { width: min(840px, calc(100vw - 32px)); height: min(760px, calc(100dvh - 40px)); }.assistant-page-dialog-conversation .assistant-page-dialog-body { display: flex; flex-direction: column; overflow: hidden; flex: 1; padding: 16px 20px; }.assistant-page-dialog-conversation .record-feed { flex: 1; min-height: 0; }.assistant-page-dialog-conversation .record-feed-scroll { min-height: 0; }
108
+ .assistant-page-chips { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }.assistant-skill-content { display: grid; gap: 22px; }.assistant-skill-description { line-height: 1.6; margin: 0; color: var(--mute); }.assistant-skill-content h3 { margin: 0 0 12px; font-size: 14px; font-weight: 700; }.assistant-skill-triggers { display: flex; flex-wrap: wrap; gap: 8px; list-style: none; padding: 0; margin: 0; }.assistant-skill-triggers li { padding: 5px 10px; border: 1px solid var(--line); border-radius: 6px; font-size: 12px; }.assistant-skill-steps { margin: 0; padding-left: 22px; list-style: decimal; }.assistant-skill-steps li { padding: 10px 0 10px 6px; border-bottom: 1px solid var(--line); line-height: 1.6; }.assistant-skill-steps li:last-child { border: 0; }.assistant-skill-steps small { display: block; color: var(--mute); }.assistant-page-warning { color: var(--warn); font-size: 13px; line-height: 1.6; margin: 0; }.assistant-page-notice { color: var(--ok); font-size: 13px; margin: 0 0 12px; }
109
+ .assistant-memory-area { padding-top: 20px; }.assistant-memory-group { max-width: 380px; margin-bottom: 20px; }.assistant-memory-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-bottom: 16px; }.assistant-memory-toolbar > span { color: var(--mute); font-size: 13px; }.assistant-memory-list { list-style: none; padding: 0; margin: 0; }.assistant-memory-list > li { display: flex; justify-content: space-between; gap: 20px; padding: 20px; border-bottom: 1px solid var(--line); }.assistant-memory-list > li:last-child { border: 0; }.assistant-memory-list > li > div:first-child { min-width: 0; }.assistant-memory-list strong { font-size: 13px; overflow-wrap: anywhere; }.assistant-memory-list p { color: var(--mute); margin: 8px 0 0; line-height: 1.6; white-space: pre-wrap; overflow-wrap: anywhere; }
110
+ @media (max-width:600px) { .assistant-admin-page { gap: 16px; }.assistant-page-heading h1 { font-size: 24px; }.assistant-page-refresh { min-width: 38px; justify-content: center; }.assistant-page-refresh > span { display: none; }.assistant-page-list th, .assistant-page-list td { padding: 12px; font-size: 12px; }.assistant-page-list th:first-child { width: 44%; }.assistant-page-list th { font-size: 10px; letter-spacing: .04em; padding: 12px 10px; white-space: nowrap; }.assistant-page-list td { padding: 12px 10px; }.assistant-page-list td small { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }.assistant-page-dialog { width: calc(100vw - 20px); max-height: calc(100dvh - 24px); }.assistant-page-dialog > header { padding: 16px; }.assistant-page-dialog-body { padding: 18px 16px; }.assistant-page-dialog > footer { padding: 14px 16px; }.assistant-page-dialog-conversation { height: calc(100dvh - 24px); }.assistant-page-dialog-conversation .assistant-page-dialog-body { padding: 12px; }.assistant-memory-toolbar { align-items: flex-start; flex-direction: column; gap: 10px; }.assistant-memory-toolbar > .assistant-page-actions { width: 100%; justify-content: flex-end; }.assistant-memory-list > li { padding: 16px; gap: 8px; } }
111
+
112
+ .assistant-page-dialog-history { height: fit-content; }.assistant-page-dialog-history .record-feed { flex: initial; height: auto; min-height: 0; max-height: calc(100dvh - 280px); }
113
+ .assistant-page-dialog-conversation .attachment-drop { min-block-size: 36px; inline-size: auto; padding: 7px 10px; border-style: solid; }
@@ -1,12 +1,68 @@
1
- .domains-header,.domains-actions,.domains-overview-actions{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap}.domains-header{margin-bottom:16px}.domains-actions,.domains-overview-actions{justify-content:flex-start}.domains-header p{margin:0;max-width:60ch}.domains-scope{overflow-wrap:anywhere}.domains-advanced{margin-top:16px;padding:16px;border:1px solid var(--line);border-radius:8px;background:var(--surface)}.domains-advanced summary{font-weight:650;cursor:pointer}.domains-overview-actions{margin-bottom:18px}.domains-registrar-settings{display:grid;gap:14px}.domains-registrar-settings label{display:flex;gap:10px;align-items:center}.domains-demo{display:grid;gap:16px;min-width:0}.domains-demo .seg{flex-wrap:wrap}@media(max-width:560px){.domains-header{align-items:flex-start;flex-direction:column}.domains-actions{width:100%}.domains-actions .btn{flex:1 1 auto;white-space:normal}}
1
+ .domains-page,.domains-page>* { min-width:0; }
2
+ .domains-header,.domains-actions,.domains-overview-actions,.domains-detail-header,.domains-detail-subtitle,.domains-task-help { display:flex;align-items:center;gap:10px; }
3
+ .domains-header { justify-content:space-between;flex-wrap:wrap;margin-bottom:24px;gap:16px; }
4
+ .domains-header h1 { margin:4px 0 8px;font-size:26px;line-height:1.2;font-weight:750;letter-spacing:-.025em;color:var(--ink-strong); }
5
+ .domains-header p { margin:0; }
6
+ .domains-actions,.domains-overview-actions { flex-wrap:wrap; }
7
+ .domains-action-menu { display:grid;gap:4px;padding:6px; }
8
+ .domains-action-menu .btn { justify-content:flex-start; }
9
+ .domains-page .toolbar-heading .panel-title { font-size:19px; }
10
+ .domains-scope,.domains-detail code { overflow-wrap:anywhere; }
11
+ .domains-narrow-scope,.domains-narrow-status { display:none; }
12
+ .domains-detail-header { justify-content:space-between;align-items:flex-start;padding:22px 24px 18px; }
13
+ .domains-detail-header>div { min-width:0; }
14
+ .domains-detail-header h2 { font-size:21px;overflow-wrap:anywhere;margin:0 0 8px; }
15
+ .domains-detail-subtitle { color:var(--mute);flex-wrap:wrap; }
16
+ .domains-detail>.mojo-tabs>.mojo-tab-list { padding:0 24px; }
17
+ .domains-detail-panel { padding:20px 24px;min-width:0; }
18
+ .domains-detail .flat-row { gap:12px; }
19
+ .domains-detail .flat-value { overflow-wrap:anywhere; }
20
+ .domains-overview-actions { margin-top:22px; }
21
+ .domains-assignment,.domains-registrar-settings { display:grid;gap:16px; }
22
+ .domains-assignment>.dim { margin:0; }
23
+ .domains-inline-action { justify-self:start; }
24
+ .domains-demo { display:grid;gap:16px;min-width:0;grid-template-columns:minmax(0,1fr); }
25
+ .domains-demo>* { min-width:0; }
26
+ .domains-demo .seg { flex-wrap:wrap; }
27
+ .domain-onboarding>.dim { margin:4px 0 20px; }
28
+ .domain-onboarding .dns-registrar-grid { gap:18px; }
29
+ .domain-onboarding .dns-onboarding-options { grid-template-columns:1fr;gap:10px;margin-top:20px; }
30
+ .domain-onboarding .dns-onboarding-options>button { grid-template-columns:26px 1fr;column-gap:14px;row-gap:3px;padding:16px; }
31
+ .domain-onboarding .dns-onboarding-options>button>i { grid-row:1/3;font-size:22px;align-self:center; }
32
+ .domain-onboarding .dns-onboarding-options>button>span { grid-column:2; }
33
+ @media(max-width:560px) {
34
+ .domains-header { align-items:flex-start; }
35
+ .domains-actions { width:100%; }
36
+ .domains-actions .btn-primary { margin-left:auto; }
37
+ .domains-detail-header { padding:18px 16px; }
38
+ .domains-detail-header h2 { font-size:19px; }
39
+ .domains-detail>.mojo-tabs>.mojo-tab-list { padding:0 12px; }
40
+ .domains-detail .mojo-tab { padding-left:10px;padding-right:10px; }
41
+ .domains-detail-panel { padding:18px 16px; }
42
+ .domains-detail .flat-row { grid-template-columns:100px minmax(0,1fr);gap:10px; }
43
+ .domains-detail .flat-action:empty { display:none; }
44
+ .domains-detail .flat-value>.btn { margin-top:8px; }
45
+ .domains-detail .dns-record-toolbar { align-items:stretch; }
46
+ .domains-detail .dns-toolbar-actions { flex-wrap:wrap; }
47
+ .domains-narrow-scope { display:inline; }
48
+ .domains-narrow-status { display:block; }
49
+ .domains-page .tbl { table-layout:fixed;width:100%; }
50
+ .domains-page .tbl th:first-child { width:auto; }
51
+ .domains-page .tbl th:last-child { width:128px; }
52
+ .domains-page .tbl td:last-child .btn { white-space:nowrap; }
53
+ .domains-page .tbl th,.domains-page .tbl td { padding-left:12px;padding-right:12px;overflow-wrap:anywhere; }
54
+ }
55
+ .domains-registration .domains-overview-actions:first-child { margin-top:0; }
56
+ .domains-registration .detail-grid,.domains-registration-contact .detail-grid { margin:20px 0;display:grid; }
57
+ .domains-registration .detail-grid>div,.domains-registration-contact .detail-grid>div { display:grid;grid-template-columns:minmax(130px,.45fr) minmax(0,1fr);gap:16px;padding:11px 0;border-bottom:1px solid var(--line); }
58
+ .domains-registration .detail-grid dt,.domains-registration-contact .detail-grid dt { color:var(--mute); }
59
+ .domains-registration .detail-grid dd,.domains-registration-contact .detail-grid dd { margin:0;overflow-wrap:anywhere; }
60
+ .domains-registration-contact>.dim { margin:0 0 20px; }
61
+ .domains-registration-contact .mojo-tab-panel,.dns-registrant-scoped .mojo-tab-panel { padding-top:18px; }
62
+ @media(max-width:560px) {
63
+ .domains-registration .detail-grid>div,.domains-registration-contact .detail-grid>div { grid-template-columns:minmax(100px,.45fr) minmax(0,1fr);gap:10px; }
64
+ .domains-registration-contact .col-6,.dns-registrant-scoped .col-6 { grid-column:1/-1; }
65
+ .dns-registrant-scoped .form-grid>.col-6:nth-child(-n+2) { grid-column:span 6; }
66
+ }
2
67
 
3
- .domains-header h1 { margin: 4px 0 8px; font-size: 26px; line-height: 1.2; font-weight: 750; letter-spacing: -.025em; color: var(--ink-strong); }
4
- .domains-header { align-items: center; margin-bottom: 24px; }
5
-
6
- .domains-page, .domains-page > * { min-width: 0; }
7
- .domains-demo { grid-template-columns: minmax(0, 1fr); }
8
- .domains-demo > * { min-width: 0; }
9
- @media (max-width: 560px) { .domains-header { align-items: flex-start; } }
10
-
11
- .domain-onboarding > .dim { margin: 4px 0 20px; }
12
- .domain-onboarding .dns-registrar-grid { gap: 20px; }
68
+ .dns-registrant-scoped .dns-contact-status { flex-direction:row;align-items:center;justify-content:flex-start; }
@@ -115,6 +115,7 @@
115
115
  .edge-vhost-editor .edge-route-row { align-items: end; }
116
116
  .edge-hostname-detail .edge-route-actions .btn { white-space: nowrap; }
117
117
 
118
+ .edge-serving-actions { display:flex;flex-wrap:wrap;gap:8px; }
118
119
  .edge-serving-heading.page-header { align-items: flex-start; }
119
120
  .edge-serving-heading.page-header > div { min-width: 0; }
120
121
  .edge-serving-evidence .edge-row-actions { align-items: center; gap: 6px; }
@@ -3,6 +3,7 @@
3
3
  because it names a token rather than a color. */
4
4
 
5
5
  /* ── Page shell ─────────────────────────────────────────────────── */
6
+ .jobs-page-links { display:flex;flex-wrap:wrap;gap:8px; }
6
7
  .jobs-dashboard { display: grid; gap: 14px; }
7
8
  .jobs-page-head {
8
9
  display: flex; align-items: flex-start; justify-content: space-between; gap: 16px;
@@ -115,6 +115,15 @@ function countDestinations(items: MenuItem[]): number {
115
115
  + countDestinations(item.children ?? []), 0);
116
116
  }
117
117
 
118
+ /** Keep secondary tools searchable without filling the everyday navigation. */
119
+ function primaryTree(item: MenuItem, pathname: string): MenuItem | null {
120
+ if (item.searchOnly && !treeOwnsRoute(item, pathname)) return null;
121
+ if (!item.children) return item;
122
+ const children = item.children.map(child => primaryTree(child, pathname))
123
+ .filter((child): child is MenuItem => child != null);
124
+ return !item.route && children.length === 0 ? null : { ...item, children };
125
+ }
126
+
118
127
  function treeOwnsRoute(item: MenuItem, pathname: string): boolean {
119
128
  if (item.route && routesMatch(item.route, pathname, item.exact)) return true;
120
129
  return (item.children ?? []).some((child) => treeOwnsRoute(child, pathname));
@@ -299,7 +308,7 @@ export function SidebarNav({ collapsed = false, onRequestExpand }: SidebarNavPro
299
308
  const normalizedQuery = query.trim().toLocaleLowerCase();
300
309
  const filteredItems = useMemo(() => normalizedQuery
301
310
  ? retainRunDividers(items, items.map((item) => filterTree(item, normalizedQuery)))
302
- : items, [items, normalizedQuery]);
311
+ : retainRunDividers(items, items.map(item => primaryTree(item, pathname))), [items, normalizedQuery, pathname]);
303
312
  const resultCount = countDestinations(filteredItems);
304
313
 
305
314
  if (!menu) return null;
@@ -29,6 +29,8 @@ export interface MenuItem {
29
29
  label?: string;
30
30
  /** Extra quick-navigation terms that are never rendered. */
31
31
  keywords?: string[];
32
+ /** In accordion menus, show only in search or while this destination is active. */
33
+ searchOnly?: boolean;
32
34
  icon?: string;
33
35
  /** Absolute route within the hash router, e.g. '/users'. */
34
36
  route?: string;