From e00ea9fb0c756c9e6e4cf6362788117c80fdc547 Mon Sep 17 00:00:00 2001 From: oss-sync Date: Fri, 5 Jun 2026 06:05:30 +0000 Subject: [PATCH] sync: update from private repo (d8074a7) --- .gitignore | 4 ++ src/config-manager.test.ts | 32 ++++++++- src/config-manager.ts | 20 ++++-- ui/index.html | 20 +++++- ui/src/App.tsx | 18 ++--- .../components/activity/ActivityEventCard.tsx | 2 +- ui/src/components/browser/PipButton.tsx | 2 +- .../browser/SaveRecordingButton.tsx | 2 +- ui/src/components/chat/ChatMessage.tsx | 6 +- ui/src/components/chat/ChatPane.tsx | 14 ++-- ui/src/components/chat/SubtaskInlineCard.tsx | 4 +- .../components/create/AttachmentDropzone.tsx | 2 +- ui/src/components/create/CreateTaskDialog.tsx | 2 +- .../components/dashboard/AddWidgetDialog.tsx | 2 +- .../components/dashboard/MarkdownWidget.tsx | 4 +- ui/src/components/dashboard/SideInfoPanel.tsx | 2 +- .../components/detail/ContextUsageGauge.tsx | 2 +- .../detail/ContinueWithPieceDialog.tsx | 2 +- ui/src/components/detail/DetailHeader.tsx | 10 +-- ui/src/components/detail/DetailPanel.tsx | 8 +-- ui/src/components/detail/tabs/BrowserTab.tsx | 8 +-- ui/src/components/detail/tabs/FilesTab.tsx | 2 +- ui/src/components/detail/tabs/OutputTab.tsx | 2 +- ui/src/components/detail/tabs/OverviewTab.tsx | 12 ++-- ui/src/components/detail/tabs/ProgressTab.tsx | 4 +- .../detail/tabs/SubtaskActivitySection.tsx | 2 +- .../components/detail/tabs/SubtasksPanel.tsx | 4 +- ui/src/components/detail/tabs/TimelineTab.tsx | 2 +- ui/src/components/detail/tabs/TraceTab.tsx | 18 ++--- .../components/embed/AmazonProductsCard.tsx | 2 +- .../components/embed/AmazonProductsDetail.tsx | 2 +- ui/src/components/embed/EmbedModal.tsx | 2 +- ui/src/components/embed/MapPlacesCard.tsx | 2 +- ui/src/components/embed/MapPlacesDetail.tsx | 2 +- ui/src/components/embed/XPostsCard.tsx | 2 +- ui/src/components/embed/XPostsDetail.tsx | 2 +- ui/src/components/embed/YouTubeVideosCard.tsx | 2 +- .../components/embed/YouTubeVideosDetail.tsx | 2 +- ui/src/components/files/FileBrowser.tsx | 10 +-- ui/src/components/files/FilePreview.tsx | 14 ++-- ui/src/components/layout/NavDrawer.tsx | 2 +- ui/src/components/layout/TopBar.tsx | 2 +- ui/src/components/list/FilterBar.tsx | 8 +-- ui/src/components/list/TaskListItem.tsx | 2 +- ui/src/components/settings/BrandingForm.tsx | 6 +- ui/src/components/settings/ConfigForm.tsx | 4 +- .../settings/GatewayKeyCreateDialog.tsx | 2 +- .../settings/GatewayKeyRawKeyDialog.tsx | 2 +- .../settings/GatewayKeyUsagePanel.tsx | 2 +- ui/src/components/settings/LlmWorkersForm.tsx | 2 +- .../settings/MemoryLearningForm.tsx | 28 ++++---- ui/src/components/settings/ModelSelect.tsx | 4 +- .../components/settings/MovementAccordion.tsx | 2 +- ui/src/components/settings/MovementForm.tsx | 2 +- ui/src/components/settings/PieceEditor.tsx | 4 +- ui/src/components/settings/PieceMetaForm.tsx | 2 +- ui/src/components/settings/ReflectionForm.tsx | 2 +- ui/src/components/settings/RulesTable.tsx | 2 +- ui/src/components/settings/SecretInput.tsx | 4 +- .../components/settings/SettingsSidebar.tsx | 2 +- ui/src/components/settings/SkillsForm.tsx | 8 +-- ui/src/components/settings/SshAuditLog.tsx | 2 +- .../settings/SshGlobalConnectionsForm.tsx | 4 +- ui/src/components/settings/SshGrantsForm.tsx | 8 +-- .../settings/SshMasterKeyRotationForm.tsx | 4 +- ui/src/components/settings/ToolTagInput.tsx | 2 +- ui/src/components/settings/formUtils.tsx | 2 +- ui/src/components/shared/StatChip.tsx | 2 +- .../userfolder/AddBrowserSessionDialog.tsx | 2 +- .../userfolder/MonacoFileEditor.tsx | 4 +- ui/src/components/userfolder/NotesPanel.tsx | 16 ++--- ui/src/components/userfolder/PetsPanel.tsx | 2 +- .../userfolder/SaveAsScriptDialog.tsx | 8 +-- .../userfolder/SshConnectionForm.tsx | 2 +- .../userfolder/SshConnectionsPanel.tsx | 2 +- .../userfolder/SshHostKeyDialog.tsx | 4 +- .../userfolder/SshPublicKeyDialog.tsx | 4 +- .../userfolder/SubscriptionsPanel.tsx | 6 +- .../components/userfolder/UserFolderTab.tsx | 4 +- ui/src/index.css | 60 ++++++++++++++--- ui/src/lib/theme-css-parity.test.ts | 34 ++++++++++ ui/src/lib/theme.test.ts | 67 +++++++++++++++++++ ui/src/lib/theme.ts | 53 +++++++++++++++ ui/src/main.tsx | 5 ++ ui/src/pages/AdminCaptchaPage.tsx | 8 +-- ui/src/pages/HelpPage.tsx | 4 +- ui/src/pages/PiecesPage.tsx | 6 +- ui/src/pages/SchedulesPage.tsx | 42 ++++++------ ui/src/pages/SharedView.tsx | 16 ++--- ui/src/pages/UsersPage.tsx | 28 ++++---- ui/tailwind.config.js | 40 ++++++----- 91 files changed, 530 insertions(+), 257 deletions(-) create mode 100644 ui/src/lib/theme-css-parity.test.ts create mode 100644 ui/src/lib/theme.test.ts create mode 100644 ui/src/lib/theme.ts diff --git a/.gitignore b/.gitignore index b595414..793d266 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,10 @@ logs/ .claude/ .playwright-mcp/ .superpowers/ +# Agent skill installs (modern-web-guidance etc. via `skills add`) — local tooling, +# not project source. +.agents/ +skills-lock.json orch.pid .server.pid src/generated/ diff --git a/src/config-manager.test.ts b/src/config-manager.test.ts index 6ddfd9d..183a5d4 100644 --- a/src/config-manager.test.ts +++ b/src/config-manager.test.ts @@ -1,6 +1,6 @@ // src/config-manager.test.ts import { describe, it, expect, beforeEach } from 'vitest'; -import { mkdtempSync, writeFileSync, readFileSync } from 'fs'; +import { mkdtempSync, writeFileSync, readFileSync, existsSync } from 'fs'; import { join } from 'path'; import { tmpdir } from 'os'; import { ConfigManager } from './config-manager.js'; @@ -85,6 +85,36 @@ describe('ConfigManager', () => { expect(cm.getConfig().provider.workers[0]?.model).toBe('new-model'); }); + it('creates config.yaml on first save when the file does not exist (fresh install on defaults)', () => { + // Fresh OSS deploy: the server boots on defaults with no config.yaml on + // disk. The first Settings-UI save must CREATE the file, not crash with + // ENOENT trying to back up a non-existent file. + const freshPath = join(tempDir, 'fresh-config.yaml'); + expect(existsSync(freshPath)).toBe(false); + + const cm = new ConfigManager(freshPath); + const result = cm.updateConfig({ + llm: { + workers: [{ + id: 'w1', + connectionType: 'direct', + endpoint: 'http://host:11434/v1', + model: 'qwen3:8b', + roles: ['auto', 'fast'], + maxConcurrency: 1, + enabled: true, + }], + }, + }); + + expect(result.ok).toBe(true); + expect(existsSync(freshPath)).toBe(true); + const raw = readFileSync(freshPath, 'utf-8'); + expect(raw).toContain('config_version: 2'); + expect(raw).toContain('http://host:11434/v1'); + expect(cm.getConfig().provider.workers[0]?.model).toBe('qwen3:8b'); + }); + it('rejects invalid config (unparseable YAML file)', () => { const cm = new ConfigManager(configPath); // Corrupt the file, then try to reload — loadConfig will fall back to defaults diff --git a/src/config-manager.ts b/src/config-manager.ts index 9cdd135..54f5ded 100644 --- a/src/config-manager.ts +++ b/src/config-manager.ts @@ -1,6 +1,6 @@ // src/config-manager.ts import { EventEmitter } from 'events'; -import { readFileSync, writeFileSync, statSync } from 'fs'; +import { readFileSync, writeFileSync, statSync, existsSync, unlinkSync } from 'fs'; import { stringify } from 'yaml'; import { loadConfig, toSnakeKeys, type AppConfig } from './config.js'; import { createHash } from 'crypto'; @@ -155,15 +155,25 @@ export class ConfigManager { const snakeConfig = toSnakeKeys(merged) as Record; const yamlStr = stringify(snakeConfig, { lineWidth: 120 }); - // Validate BEFORE writing: backup, write, validate, rollback on failure - const backupContent = readFileSync(this.configPath, 'utf-8'); + // Validate BEFORE writing: backup, write, validate, rollback on failure. + // config.yaml may not exist yet — a fresh install boots on defaults with no + // file on disk, and the first save must CREATE it rather than ENOENT trying + // to back up a missing file. With no prior content, a failed save is rolled + // back by removing the file we just created. + const backupContent = existsSync(this.configPath) + ? readFileSync(this.configPath, 'utf-8') + : null; try { writeFileSync(this.configPath, yamlStr, 'utf-8'); logger.info(`[config-manager] config written to ${this.configPath}`); this.currentConfig = loadConfig(this.configPath); } catch (e) { - // Restore original file on validation failure - writeFileSync(this.configPath, backupContent, 'utf-8'); + // Restore original on validation failure (or drop the file we created). + if (backupContent !== null) { + writeFileSync(this.configPath, backupContent, 'utf-8'); + } else { + try { unlinkSync(this.configPath); } catch { /* nothing to revert */ } + } logger.warn(`[config-manager] config update failed, reverted: ${e}`); return { ok: false, errors: e, message: 'Invalid config — changes reverted' }; } diff --git a/ui/index.html b/ui/index.html index 91b976a..a73135a 100644 --- a/ui/index.html +++ b/ui/index.html @@ -3,6 +3,21 @@ + + @@ -23,10 +38,13 @@ min-height: 100dvh; } + html { background: #ffffff; } + html[data-theme="dark"] { background: #0a0a0c; } + body { margin: 0; overflow-x: hidden; - background: #f3f6fb; + background: transparent; } input, diff --git a/ui/src/App.tsx b/ui/src/App.tsx index 8b33201..770ea7e 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -411,7 +411,7 @@ function AppInner({ isAdmin, authEnabled, user }: { isAdmin: boolean; authEnable
{!panelOpen ? (
-
+
} activeWidgetSlug={dashboardWidget} @@ -421,7 +421,7 @@ function AppInner({ isAdmin, authEnabled, user }: { isAdmin: boolean; authEnable
) : ( setUrlState(prev => ({ ...prev, mobileTab: id }))} onSwipeRightFromEdge={() => setUrlState(prev => ({ ...prev, taskId: null, mobileTab: 'chat' as MobileTabId }))} visibleTabs={mobileVisibleTabIds}> -
+
{mobileVisibleTabs.map(({ id, label }) => (
) : streamingText ? ( -
+
{streamingText}
@@ -366,7 +366,7 @@ export function ChatPane({ task, comments, onSubmit, onCancel, onOpenDetail }: C {!isAtBottom && (
{/* Composer */} -
+
{isBusy && (
void handleSubmit()} disabled={submitting} - className="flex-shrink-0 px-2 h-6 bg-white border border-red-200 rounded text-[10px] font-medium text-red-700 hover:bg-red-100 disabled:opacity-50" + className="flex-shrink-0 px-2 h-6 bg-canvas border border-red-200 rounded text-[10px] font-medium text-red-700 hover:bg-red-100 disabled:opacity-50" > 再送信 @@ -461,7 +461,7 @@ export function ChatPane({ task, comments, onSubmit, onCancel, onOpenDetail }: C diff --git a/ui/src/components/dashboard/SideInfoPanel.tsx b/ui/src/components/dashboard/SideInfoPanel.tsx index e7e4748..f41b05c 100644 --- a/ui/src/components/dashboard/SideInfoPanel.tsx +++ b/ui/src/components/dashboard/SideInfoPanel.tsx @@ -30,7 +30,7 @@ export function SideInfoPanel({ const activeWidget = widgets.find(w => w.slug === activeSlug); return ( -
+
+
{label} diff --git a/ui/src/components/detail/ContinueWithPieceDialog.tsx b/ui/src/components/detail/ContinueWithPieceDialog.tsx index 4487e39..cb04544 100644 --- a/ui/src/components/detail/ContinueWithPieceDialog.tsx +++ b/ui/src/components/detail/ContinueWithPieceDialog.tsx @@ -67,7 +67,7 @@ export function ContinueWithPieceDialog({ className="fixed inset-0 z-50 flex items-center justify-center bg-black/40" onClick={e => { if (e.target === e.currentTarget) onClose(); }} > -
+
{/* Header */}
diff --git a/ui/src/components/detail/DetailHeader.tsx b/ui/src/components/detail/DetailHeader.tsx index 3816e35..76f6c05 100644 --- a/ui/src/components/detail/DetailHeader.tsx +++ b/ui/src/components/detail/DetailHeader.tsx @@ -70,7 +70,7 @@ function ShareButton({ taskId, shareToken, onShareChange }: { taskId: number; sh disabled={shareMutation.isPending} title={shareMutation.isPending ? '共有中...' : '公開リンクを発行'} aria-label="公開リンクを発行" - className={`${iconBtnBase} border-hairline bg-white text-slate-600 hover:text-slate-900 hover:bg-surface`} + className={`${iconBtnBase} border-hairline bg-canvas text-slate-600 hover:text-slate-900 hover:bg-surface`} > {shareMutation.isPending ? ( @@ -102,7 +102,7 @@ function ShareButton({ taskId, shareToken, onShareChange }: { taskId: number; sh onClick={handleCopy} title={copied ? 'コピーしました' : '共有リンクをコピー'} aria-label="共有リンクをコピー" - className={`${iconBtnBase} ${copied ? 'border-emerald-200 bg-emerald-50 text-emerald-700' : 'border-hairline bg-white text-slate-600 hover:text-slate-900 hover:bg-surface'}`} + className={`${iconBtnBase} ${copied ? 'border-emerald-200 bg-emerald-50 text-emerald-700' : 'border-hairline bg-canvas text-slate-600 hover:text-slate-900 hover:bg-surface'}`} > {copied ? ( @@ -120,7 +120,7 @@ function ShareButton({ taskId, shareToken, onShareChange }: { taskId: number; sh disabled={unshareMutation.isPending} title="共有を停止" aria-label="共有を停止" - className={`${iconBtnBase} border-hairline bg-white text-slate-500 hover:text-red-700 hover:border-red-200 hover:bg-red-50`} + className={`${iconBtnBase} border-hairline bg-canvas text-slate-500 hover:text-red-700 hover:border-red-200 hover:bg-red-50`} > {unshareMutation.isPending ? ( @@ -150,7 +150,7 @@ function ContinueButton({ latestJobStatus, onClick }: { latestJobStatus: string disabled={!enabled} title={enabled ? '別 piece で続ける' : 'タスクが進行中のため続行できません'} aria-label="別 piece で続ける" - className={`${iconBtnBase} border-hairline bg-white text-slate-600 hover:text-slate-900 hover:bg-surface`} + className={`${iconBtnBase} border-hairline bg-canvas text-slate-600 hover:text-slate-900 hover:bg-surface`} > {/* arrow → divider: 「次のフェーズへ進む」cue。FileBrowser の refresh (循環矢印) と区別するためフラットな skip-forward 形状を採用 */} @@ -168,7 +168,7 @@ export function DetailHeader({ title, subtitle, tabs, activeTab, tabTransitionPe // renders its own mobile-level top tab bar with the same controls. // Two close buttons / two tab bars on iPhone was visually redundant. return ( -
+
{subtitle}
diff --git a/ui/src/components/detail/DetailPanel.tsx b/ui/src/components/detail/DetailPanel.tsx index 17f7020..c3a36e1 100644 --- a/ui/src/components/detail/DetailPanel.tsx +++ b/ui/src/components/detail/DetailPanel.tsx @@ -195,7 +195,7 @@ export function LocalDetailPanel({ )}
{editingVisibility && ( -
+