From 84c8ac67ed71233c92ee3916ecfad847ee19f4b3 Mon Sep 17 00:00:00 2001 From: toki Date: Sun, 24 May 2026 21:48:51 +0900 Subject: [PATCH] =?UTF-8?q?=EA=B8=B0=EB=8A=A5:=20monorepo=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=EB=A5=BC=20=EB=B3=80=EA=B2=BD=ED=95=98=EA=B3=A0=20web?= =?UTF-8?q?=EC=9D=84=20=EC=A0=9C=EA=B1=B0=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - apps/web 디렉터리 전체를 삭제하여 web 클라이언트 제거 - Flutter 모바일 앱 구조를 정리하고 모델/스크린/서비스 추가 - agent-ops 규칙 및 로드맵 업데이트 - monorepo 문서 갱신 --- README.md | 4 +- .../flutter-first-client-consolidation.md | 8 +- .../project-workspace-management-ux.md | 2 +- .../rules/project/domain/contracts/rules.md | 6 +- agent-ops/rules/project/domain/core/rules.md | 6 +- .../rules/project/domain/mobile/rules.md | 4 +- agent-ops/rules/project/domain/web/rules.md | 66 - .../project/domain/workspace-ops/rules.md | 4 +- agent-ops/rules/project/rules.md | 3 - apps/mobile/lib/main.dart | 17 +- apps/mobile/lib/models/project_workspace.dart | 50 + .../lib/screens/workspace_home_page.dart | 442 +++++++ .../lib/services/workspace_launcher.dart | 12 + .../flutter/generated_plugin_registrant.cc | 4 + .../linux/flutter/generated_plugins.cmake | 1 + .../Flutter/GeneratedPluginRegistrant.swift | 2 + apps/mobile/pubspec.lock | 66 +- apps/mobile/pubspec.yaml | 1 + apps/mobile/test/widget_test.dart | 41 +- .../flutter/generated_plugin_registrant.cc | 3 + .../windows/flutter/generated_plugins.cmake | 1 + apps/web/.env.example | 1 - apps/web/.gitignore | 13 - apps/web/README.md | 49 - apps/web/index.html | 12 - apps/web/package-lock.json | 1174 ----------------- apps/web/package.json | 32 - apps/web/src/api/client.ts | 36 - apps/web/src/api/projects.ts | 88 -- apps/web/src/api/settings.ts | 68 - apps/web/src/api/tasks.ts | 41 - apps/web/src/app/App.tsx | 26 - apps/web/src/app/router.tsx | 47 - .../src/components/agent/AgentContextCard.tsx | 24 - apps/web/src/components/agent/AgentInput.tsx | 37 - .../src/components/agent/AgentMessageList.tsx | 21 - apps/web/src/components/common/Button.tsx | 21 - apps/web/src/components/common/Card.tsx | 20 - apps/web/src/components/common/EmptyState.tsx | 13 - .../src/components/projects/ProjectCard.tsx | 55 - .../src/components/projects/ProjectDetail.tsx | 63 - .../src/components/projects/WorkspaceTabs.tsx | 42 - .../components/settings/IntegrationStatus.tsx | 40 - .../components/settings/SettingsSection.tsx | 19 - apps/web/src/layout/ActivityBar.tsx | 38 - apps/web/src/layout/AgentSidebar.tsx | 37 - apps/web/src/layout/AppShell.tsx | 61 - apps/web/src/layout/MobileNav.tsx | 34 - apps/web/src/layout/TopBar.tsx | 51 - apps/web/src/main.tsx | 11 - apps/web/src/routes/AgentPage.tsx | 80 -- apps/web/src/routes/ProjectsPage.tsx | 70 - apps/web/src/routes/SettingsPage.tsx | 55 - apps/web/src/stores/agentStore.ts | 85 -- apps/web/src/stores/layoutStore.ts | 17 - apps/web/src/stores/projectStore.ts | 35 - apps/web/src/styles/global.css | 861 ------------ apps/web/src/vite-env.d.ts | 1 - apps/web/tsconfig.json | 21 - apps/web/vite.config.ts | 6 - docs/monorepo.md | 2 +- packages/contracts/README.md | 6 + .../notes/flutter-core-api-candidates.md | 196 +++ 63 files changed, 844 insertions(+), 3508 deletions(-) delete mode 100644 agent-ops/rules/project/domain/web/rules.md create mode 100644 apps/mobile/lib/models/project_workspace.dart create mode 100644 apps/mobile/lib/screens/workspace_home_page.dart create mode 100644 apps/mobile/lib/services/workspace_launcher.dart delete mode 100644 apps/web/.env.example delete mode 100644 apps/web/.gitignore delete mode 100644 apps/web/README.md delete mode 100644 apps/web/index.html delete mode 100644 apps/web/package-lock.json delete mode 100644 apps/web/package.json delete mode 100644 apps/web/src/api/client.ts delete mode 100644 apps/web/src/api/projects.ts delete mode 100644 apps/web/src/api/settings.ts delete mode 100644 apps/web/src/api/tasks.ts delete mode 100644 apps/web/src/app/App.tsx delete mode 100644 apps/web/src/app/router.tsx delete mode 100644 apps/web/src/components/agent/AgentContextCard.tsx delete mode 100644 apps/web/src/components/agent/AgentInput.tsx delete mode 100644 apps/web/src/components/agent/AgentMessageList.tsx delete mode 100644 apps/web/src/components/common/Button.tsx delete mode 100644 apps/web/src/components/common/Card.tsx delete mode 100644 apps/web/src/components/common/EmptyState.tsx delete mode 100644 apps/web/src/components/projects/ProjectCard.tsx delete mode 100644 apps/web/src/components/projects/ProjectDetail.tsx delete mode 100644 apps/web/src/components/projects/WorkspaceTabs.tsx delete mode 100644 apps/web/src/components/settings/IntegrationStatus.tsx delete mode 100644 apps/web/src/components/settings/SettingsSection.tsx delete mode 100644 apps/web/src/layout/ActivityBar.tsx delete mode 100644 apps/web/src/layout/AgentSidebar.tsx delete mode 100644 apps/web/src/layout/AppShell.tsx delete mode 100644 apps/web/src/layout/MobileNav.tsx delete mode 100644 apps/web/src/layout/TopBar.tsx delete mode 100644 apps/web/src/main.tsx delete mode 100644 apps/web/src/routes/AgentPage.tsx delete mode 100644 apps/web/src/routes/ProjectsPage.tsx delete mode 100644 apps/web/src/routes/SettingsPage.tsx delete mode 100644 apps/web/src/stores/agentStore.ts delete mode 100644 apps/web/src/stores/layoutStore.ts delete mode 100644 apps/web/src/stores/projectStore.ts delete mode 100644 apps/web/src/styles/global.css delete mode 100644 apps/web/src/vite-env.d.ts delete mode 100644 apps/web/tsconfig.json delete mode 100644 apps/web/vite.config.ts create mode 100644 packages/contracts/notes/flutter-core-api-candidates.md diff --git a/README.md b/README.md index 1477ac8..2f44234 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ NomadCode는 IOP를 사용하는 개발 워크플로우 셸입니다. 사용자 - `services/core`는 health/readiness endpoint, task API, PostgreSQL persistence, goose migration, sqlc query 생성 구조, River job, IOP OpenAI-compatible Responses client 경로, 향후 A2A agent delegation scaffolding, Plane/Mattermost adapter stub을 포함합니다. - `apps/mobile`은 NomadCode 제품 UI의 기준 Flutter 앱이며 모바일/데스크톱/선택적 web target, Firebase Messaging, local notifications, HTTP client 의존성을 포함합니다. -- `apps/web`은 이전 React/Vite 운영 콘솔 scaffold입니다. 장기 제품 UI 경로에서는 제외하며, Flutter-first 전환 과정에서 제거 또는 격리 대상으로 둡니다. +- `apps/web`은 이전 React/Vite 운영 콘솔 scaffold였으며, Flutter-first 통합에 따라 완전히 폐기 및 제거되었습니다. - `packages/contracts`는 향후 OpenAPI, protobuf, generated client, fixture, compatibility asset을 담을 공유 계약 공간입니다. - 활성 계획과 Milestone 상태는 `agent-ops/roadmap/`에서 관리합니다. README에는 현재 작업 상태를 복사하지 않고 진입 문서만 연결합니다. @@ -81,7 +81,7 @@ flutter run |------|------| | `services/core/` | Go backend. orchestration, persistence, scheduling, HTTP API, 외부 service adapter를 담당합니다. | | `apps/mobile/` | Flutter 기반 제품 UI source of truth, 모바일/데스크톱 클라이언트와 notification 연동 표면입니다. | -| `apps/web/` | 이전 React/Vite 운영 콘솔 scaffold입니다. 장기 제품 경로에서 제거 또는 격리할 대상입니다. | +| `apps/web/` | [Legacy/Retired] React/Vite 운영 콘솔 scaffold입니다. Flutter-first 클라이언트 통합에 따라 완전히 폐기 및 제거되었습니다. | | `packages/contracts/` | 서비스와 클라이언트가 공유할 API/schema 계약 공간입니다. | | `docs/` | 아키텍처와 운영 문서입니다. | | `bin/` | root test, lint, build, dev helper 명령입니다. | diff --git a/agent-ops/roadmap/milestones/flutter-first-client-consolidation.md b/agent-ops/roadmap/milestones/flutter-first-client-consolidation.md index 76d37c1..35b2067 100644 --- a/agent-ops/roadmap/milestones/flutter-first-client-consolidation.md +++ b/agent-ops/roadmap/milestones/flutter-first-client-consolidation.md @@ -20,7 +20,7 @@ Client Platform Consolidation ## 범위 - Flutter 앱을 NomadCode 제품 UI의 기준 구현으로 정리 -- React/Vite 웹 콘솔 scaffold를 제거하거나 제품 경로 밖의 임시/아카이브 표면으로 격리 +- React/Vite 웹 콘솔 scaffold를 제거하여 제품 경로 밖으로 격리/폐기 완료 - root dev/test/lint/build entrypoint에서 React web 의존을 제거하거나 명확히 비활성화 - README, docs, roadmap, domain rule에서 web/app 책임 경계를 Flutter-first 기준으로 갱신 - code-server는 React wrapper를 거치지 않고 Flutter 앱에서 직접 열거나 embed하는 표면으로 정리 @@ -29,7 +29,7 @@ Client Platform Consolidation ## 필수 기능 - [x] [ui-source] Flutter 앱을 제품 UI source of truth로 선언하고 React web을 기준 구현에서 제외한다. -- [ ] [react-retire] `apps/web` React/Vite scaffold의 제거, 격리, 또는 폐기 경로를 정리한다. +- [x] [react-retire] `apps/web` React/Vite scaffold의 제거, 격리, 또는 폐기 경로를 정리한다. - [x] [entrypoints] root `bin/*` 개발/검증 entrypoint가 Flutter-first 클라이언트 구조를 반영하도록 한다. - [x] [docs-rules] README, docs, roadmap, domain rule의 web/app 설명을 Flutter-first 기준으로 맞춘다. - [ ] [codeserver-direct] code-server, workspace, diff/terminal/editor 표면은 React wrapper 없이 Flutter 앱에서 직접 연결하는 기준을 정한다. @@ -37,9 +37,9 @@ Client Platform Consolidation ## 완료 기준 -- [ ] 로드맵과 프로젝트 문서에서 React/Vite 웹 콘솔이 장기 제품 UI 기준 구현으로 설명되지 않는다. +- [x] 로드맵과 프로젝트 문서에서 React/Vite 웹 콘솔이 장기 제품 UI 기준 구현으로 설명되지 않는다. - [ ] Flutter 앱이 모바일/데스크톱/선택적 web target의 기준 클라이언트로 설명된다. -- [ ] `apps/web`의 처리 방침이 삭제, 격리, 보류 중 하나로 코드와 문서에서 일관된다. +- [x] `apps/web`의 처리 방침이 삭제, 격리, 보류 중 하나로 코드와 문서에서 일관된다. - [ ] root `bin/test`, `bin/lint`, `bin/build`, `bin/dev`가 React web 제거 이후의 검증 흐름과 충돌하지 않는다. - [ ] code-server를 React로 감싸고 다시 Flutter로 감싸는 구조를 만들지 않는다는 기준이 문서화되어 있다. - [ ] 후속 Project Workspace Management UX가 Flutter-first 클라이언트 구조 위에서 진행될 수 있다. diff --git a/agent-ops/roadmap/milestones/project-workspace-management-ux.md b/agent-ops/roadmap/milestones/project-workspace-management-ux.md index 081a434..cb04c3a 100644 --- a/agent-ops/roadmap/milestones/project-workspace-management-ux.md +++ b/agent-ops/roadmap/milestones/project-workspace-management-ux.md @@ -56,7 +56,7 @@ Project Workspace Management UX ## 작업 컨텍스트 - 관련 경로: `apps/mobile/`, `packages/contracts/`, `services/core/` -- 전환 참고 경로: `apps/web/` +- 전환 참고 경로: [Legacy/Retired] `apps/web/` (제거됨) - 선행 작업: Flutter-first Client Consolidation, External Integration - 후속 작업: 확인 필요 - 확인 필요: `구현 잠금`의 결정 필요 항목 참고. diff --git a/agent-ops/rules/project/domain/contracts/rules.md b/agent-ops/rules/project/domain/contracts/rules.md index e193b65..80593fc 100644 --- a/agent-ops/rules/project/domain/contracts/rules.md +++ b/agent-ops/rules/project/domain/contracts/rules.md @@ -18,7 +18,7 @@ NomadCode 런타임 사이의 공유 계약 도메인이다. API schema, event s - `services/core/internal/http/` — API implementation은 core 도메인이다. - `apps/mobile/lib/` — mobile-specific client/service code는 mobile 도메인이다. -- `apps/web/src/api/` — legacy web-specific API client wrapper는 web 도메인이다. +- `apps/web/` — [Legacy/Retired] 이전 React/Vite web scaffold 경로는 완전히 제거되어 제외 대상입니다. ## 주요 구성 요소 @@ -27,7 +27,7 @@ NomadCode 런타임 사이의 공유 계약 도메인이다. API schema, event s ## 유지할 패턴 - 계약 변경은 additive를 우선한다. -- breaking change는 core와 Flutter 앱 반영을 같은 branch에서 조율한다. legacy web은 제거 전 필요한 경우에만 함께 조율한다. +- breaking change는 core와 Flutter 앱 반영을 같은 branch에서 조율한다. - schema가 추가되면 sample payload나 compatibility note를 함께 둔다. - generated client가 생기면 생성 명령과 source schema를 문서화한다. @@ -35,7 +35,7 @@ NomadCode 런타임 사이의 공유 계약 도메인이다. API schema, event s - **core**: contracts는 API shape의 source-of-truth이고 core는 이를 구현한다. - **mobile**: mobile은 contracts를 소비하되 platform behavior는 mobile 도메인에서 다룬다. -- **web**: legacy web은 제거 전 필요한 경우에만 contracts를 소비하거나 generated client를 감싼다. +- **web**: [Legacy/Retired] React web 도메인은 완전히 제거되었습니다. - **workspace-ops**: contracts 생성/검증 명령이 root script가 되면 workspace-ops와 함께 갱신한다. ## 금지 사항 diff --git a/agent-ops/rules/project/domain/core/rules.md b/agent-ops/rules/project/domain/core/rules.md index c8f175e..0384c50 100644 --- a/agent-ops/rules/project/domain/core/rules.md +++ b/agent-ops/rules/project/domain/core/rules.md @@ -31,7 +31,7 @@ NomadCode의 백엔드 오케스트레이션 도메인이다. workflow, scheduli ## 제외 경로 - `apps/mobile/` — Flutter-first 제품 클라이언트이며 core 내부 패키지를 직접 참조하지 않는다. -- `apps/web/` — 이전 React/Vite scaffold이며 core 구현을 직접 소유하지 않는다. +- `apps/web/` — [Legacy/Retired] React/Vite scaffold 경로는 완전히 제거되어 제외 대상입니다. - `packages/contracts/` — 공유 API/schema 계약의 소유 영역이다. ## 주요 구성 요소 @@ -71,7 +71,7 @@ NomadCode의 백엔드 오케스트레이션 도메인이다. workflow, scheduli - 외부 서비스 호출은 `internal/adapters/` 아래에 격리한다. - 환경 변수 기본값과 alias는 `internal/config`에서 관리하고 README의 실행 예시와 어긋나지 않게 유지한다. - scheduler 변경은 가능한 한 job 단위 테스트를 추가한다. -- HTTP API 변경은 Flutter 앱 영향과 contracts 반영 필요성을 함께 판단한다. legacy web 영향은 제거 전 필요한 경우에만 확인한다. +- HTTP API 변경은 Flutter 앱 영향과 contracts 반영 필요성을 함께 판단한다. - Plane 관련 작업에서는 board state와 agent 내부 실행 상태를 같은 workflow state로 섞지 않는다. `testing`은 agent가 자체 검증과 리뷰 루프를 끝낸 뒤 사용자 테스트를 기다리는 상태로 사용한다. - Plane/Jira work item 본문(description)은 작업 요구사항과 맥락의 원본으로 취급하고, agent 실행 상태를 매번 갱신하는 저장소로 사용하지 않는다. @@ -79,7 +79,7 @@ NomadCode의 백엔드 오케스트레이션 도메인이다. workflow, scheduli - **contracts**: API shape가 클라이언트와 공유되면 contracts에 먼저 표현하거나 문서화한다. - **mobile**: mobile은 public API와 notification/auth 계약만 의존한다. -- **web**: legacy web은 제거 전 필요한 경우에만 HTTP/API client를 통해 core와 통신한다. core 내부 Go package를 전제하지 않는다. +- **web**: [Legacy/Retired] React web 도메인은 완전히 제거되었습니다. - **workspace-ops**: root helper 변경은 workspace-ops 소유이고, core-local bin 변경만 core 소유다. ## 금지 사항 diff --git a/agent-ops/rules/project/domain/mobile/rules.md b/agent-ops/rules/project/domain/mobile/rules.md index 04eee35..fc25912 100644 --- a/agent-ops/rules/project/domain/mobile/rules.md +++ b/agent-ops/rules/project/domain/mobile/rules.md @@ -22,7 +22,7 @@ NomadCode의 Flutter-first 제품 UI 도메인이다. 모바일/데스크톱/선 ## 제외 경로 - `services/core/` — backend orchestration/API implementation. -- `apps/web/` — 이전 React/Vite browser console scaffold. +- `apps/web/` — [Legacy/Retired] React/Vite scaffold 경로는 완전히 제거되어 제외 대상입니다. - `packages/contracts/` — shared API/schema contracts. ## 주요 구성 요소 @@ -50,7 +50,7 @@ NomadCode의 Flutter-first 제품 UI 도메인이다. 모바일/데스크톱/선 - **core**: mobile은 core API/notification 계약에 의존하되 core 내부 구현을 알지 않는다. - **contracts**: mobile이 사용하는 API/event shape는 contracts 후보로 올린다. -- **web**: React/Vite web은 legacy scaffold이며 Flutter 제품 UI를 복제하지 않는다. +- **web**: [Legacy/Retired] React web 도메인은 완전히 제거되었습니다. - **workspace-ops**: root scripts와 agent rules는 mobile 내부에 복제하지 않는다. ## 금지 사항 diff --git a/agent-ops/rules/project/domain/web/rules.md b/agent-ops/rules/project/domain/web/rules.md deleted file mode 100644 index a6e2841..0000000 --- a/agent-ops/rules/project/domain/web/rules.md +++ /dev/null @@ -1,66 +0,0 @@ ---- -domain: web -last_rule_review_commit: e3b5dae725db62dd7a47c563abcce557e0557551 -last_rule_updated_at: 2026-05-21 ---- - -# web - -## 목적 / 책임 - -NomadCode의 이전 React/Vite 브라우저 운영 콘솔 scaffold 도메인이다. Flutter-first 전환 이후 장기 제품 UI 경로에서는 제외하며, 이 도메인의 작업은 제거, 격리, 또는 legacy 상태 보존에 한정한다. - -## 포함 경로 - -- `apps/web/src/app/` — React app과 router 조립. -- `apps/web/src/routes/` — 페이지 단위 route. -- `apps/web/src/layout/` — shell, navigation, sidebar. -- `apps/web/src/components/` — agent, project, settings, common UI components. -- `apps/web/src/stores/` — Zustand 기반 client state. -- `apps/web/src/api/` — backend API client. -- `apps/web/src/styles/` — global style. -- `apps/web/index.html`, `apps/web/.env.example` — Vite HTML entry와 환경 변수 예시. -- `apps/web/package.json`, `apps/web/package-lock.json`, `apps/web/vite.config.ts`, `apps/web/tsconfig.json` — web build/tooling. - -## 제외 경로 - -- `services/core/` — API implementation과 persistence는 core 도메인이다. -- `apps/mobile/` — Flutter 제품 UI source of truth는 mobile 도메인이다. -- `packages/contracts/` — shared schema/source-of-truth는 contracts 도메인이다. - -## 주요 구성 요소 - -- `src/app/App.tsx` — QueryClient와 router provider 조립. -- `src/app/router.tsx` — web route 구성. -- `src/layout/AppShell.tsx` — 전체 shell. -- `src/layout/AgentSidebar.tsx` — agent context/sidebar 영역. -- `src/routes/AgentPage.tsx` — agent workspace page. -- `src/routes/ProjectsPage.tsx` — project workspace page. -- `src/routes/SettingsPage.tsx` — integration settings page. -- `src/stores/agentStore.ts` — agent interaction state. -- `src/stores/layoutStore.ts` — shell navigation/sidebar state. -- `src/stores/projectStore.ts` — project workspace state. -- `src/api/client.ts` — API 호출 공통 client. -- `src/components/settings/IntegrationStatus.tsx` — integration 상태 표시와 테스트 액션 UI. - -## 유지할 패턴 - -- 새 제품 기능을 React web에 추가하지 않는다. -- 제거 전 legacy 유지가 필요한 경우 기존 `src/routes`, `src/layout`, `src/components`, `src/api`, `src/stores` 구조를 유지한다. -- API 호출 형태가 바뀌면 Flutter 앱과 contracts/core 경계를 우선 확인하고, legacy web 변경은 필요한 최소 범위로 제한한다. -- legacy scaffold를 직접 수정할 때는 `npm run check`가 통과하도록 TypeScript type을 유지한다. - -## 다른 도메인과의 경계 - -- **core**: web은 core의 HTTP/API surface만 사용한다. -- **contracts**: 반복되는 request/response shape는 contracts 후보로 올린다. -- **mobile**: Flutter 앱이 제품 UI source of truth이며, web은 제품 UI 구현을 복제하지 않는다. -- **workspace-ops**: root `bin/*`나 monorepo 문서는 web 변경과 분리한다. - -## 금지 사항 - -- core 내부 구조를 web 컴포넌트에 하드코딩하지 않는다. -- 새 제품 화면을 React web에 추가하지 않는다. -- API mock과 실제 API shape를 장기간 다르게 유지하지 않는다. -- 공통 UI component에 page-specific side effect를 넣지 않는다. -- 대규모 visual/style 변경과 API behavior 변경을 한 커밋에 섞지 않는다. diff --git a/agent-ops/rules/project/domain/workspace-ops/rules.md b/agent-ops/rules/project/domain/workspace-ops/rules.md index ffc41ef..934135a 100644 --- a/agent-ops/rules/project/domain/workspace-ops/rules.md +++ b/agent-ops/rules/project/domain/workspace-ops/rules.md @@ -27,11 +27,11 @@ NomadCode 원레포 운영 도메인이다. 루트 helper, 문서, ignore 정책 - `agent-ops/rules/common/` — agentic-framework에서 동기화되는 공통 rules. - `agent-ops/skills/common/` — agentic-framework에서 동기화되는 공통 skills. -- `services/core/`, `apps/web/`, `apps/mobile/`, `packages/contracts/` — 각 제품 도메인 소유 코드. +- `services/core/`, `apps/web/` (Legacy/Retired), `apps/mobile/`, `packages/contracts/` — 각 제품 도메인 소유 코드. ## 주요 구성 요소 -- `bin/test` — core/web/mobile 검증 entrypoint. +- `bin/test` — core/mobile 검증 entrypoint. - `bin/lint` — vet/check/analyze entrypoint. - `bin/build` — build entrypoint. - `bin/dev` — development entrypoint 안내. diff --git a/agent-ops/rules/project/rules.md b/agent-ops/rules/project/rules.md index 16472e9..a614461 100644 --- a/agent-ops/rules/project/rules.md +++ b/agent-ops/rules/project/rules.md @@ -13,7 +13,6 @@ NomadCode는 AI 병렬 작업 운용을 위한 원레포다. 백엔드 오케스 - `services/core/` — Go 기반 백엔드, workflow, scheduler, persistence, 외부 adapter. - `apps/mobile/` — Flutter 기반 제품 UI source of truth, 모바일/데스크톱 클라이언트와 알림 연동. -- `apps/web/` — 이전 React/Vite 운영 콘솔 scaffold. Flutter-first 전환에서 제거 또는 격리 대상. - `packages/contracts/` — 서비스와 클라이언트가 공유하는 API/schema 계약. - `bin/` — 원레포 공통 test/build/lint/dev entrypoint. - `docs/` — 아키텍처와 운영 문서. @@ -30,7 +29,6 @@ NomadCode는 AI 병렬 작업 운용을 위한 원레포다. 백엔드 오케스 - 원레포 루트에서 가능한 작업은 `bin/test`, `bin/lint`, `bin/build`, `bin/dev`를 우선 사용한다. - cross-boundary 변경은 계약, 서비스, 클라이언트 반영을 같은 브랜치에서 닫는다. - `services/core` 변경은 `go test ./...` 또는 `bin/test`로 확인한다. -- `apps/web`는 장기 제품 UI 경로에서 제외한다. 제거 전 legacy scaffold를 직접 변경할 때만 `npm run check` 또는 `npm run verify`로 확인한다. - `apps/mobile` 변경은 `flutter test`와 필요 시 `flutter analyze --no-fatal-infos`로 확인한다. - `packages/contracts`가 실제 계약을 갖기 전에는 API 형태를 서비스/클라이언트에 중복 고정하지 말고 계약 후보를 문서화한다. - 하위 앱/서비스에 별도 `agent-ops`나 AI entry 파일을 만들지 않는다. 루트 `agent-ops`와 루트 entry 파일만 사용한다. @@ -41,7 +39,6 @@ NomadCode는 AI 병렬 작업 운용을 위한 원레포다. 백엔드 오케스 | 경로 패턴 | 도메인 | rules.md | |----------|--------|----------| | `services/core/**` | core | `agent-ops/rules/project/domain/core/rules.md` | -| `apps/web/**` | web | `agent-ops/rules/project/domain/web/rules.md` | | `apps/mobile/**` | mobile | `agent-ops/rules/project/domain/mobile/rules.md` | | `packages/contracts/**` | contracts | `agent-ops/rules/project/domain/contracts/rules.md` | | `bin/**`, `docs/**`, `README.md`, `.gitignore`, `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, `opencode.json`, `agent-ops/.version`, `agent-ops/bin/**`, `agent-ops/roadmap/**`, `agent-ops/rules/project/**` | workspace-ops | `agent-ops/rules/project/domain/workspace-ops/rules.md` | diff --git a/apps/mobile/lib/main.dart b/apps/mobile/lib/main.dart index ded0e85..f732056 100644 --- a/apps/mobile/lib/main.dart +++ b/apps/mobile/lib/main.dart @@ -6,9 +6,12 @@ import 'package:flutter/material.dart'; import 'services/mattermost_auth_service.dart'; import 'services/push_notification_background.dart'; import 'services/push_notification_service.dart'; +import 'services/workspace_launcher.dart'; +import 'screens/workspace_home_page.dart'; final _navigatorKey = GlobalKey(); final _pushService = PushNotificationService(); +final _launcher = WorkspaceLauncher(); Future _applyFullscreenMode() async { await SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky); @@ -50,7 +53,11 @@ class MyApp extends StatelessWidget { title: 'nomadcode-app', navigatorKey: _navigatorKey, theme: ThemeData( - colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), + colorScheme: ColorScheme.fromSeed( + seedColor: Colors.deepPurple, + brightness: Brightness.dark, + ), + useMaterial3: true, ), home: const MyHomePage(title: 'nomadcode-app'), builder: (context, child) { @@ -111,10 +118,10 @@ class _MyHomePageState extends State { @override Widget build(BuildContext context) { - return Scaffold( - body: const Center( - child: Text('nomadcode-app'), - ), + return WorkspaceHomePage( + onLaunchCodeServer: (project) async { + return _launcher.openCodeServer(project); + }, ); } } diff --git a/apps/mobile/lib/models/project_workspace.dart b/apps/mobile/lib/models/project_workspace.dart new file mode 100644 index 0000000..0819d4a --- /dev/null +++ b/apps/mobile/lib/models/project_workspace.dart @@ -0,0 +1,50 @@ +enum ProjectStatus { + active, + paused, + pending, +} + +class ProjectWorkspace { + final String id; + final String name; + final String description; + final ProjectStatus status; + final String codeServerUrl; + final int tasksCount; + + const ProjectWorkspace({ + required this.id, + required this.name, + required this.description, + required this.status, + required this.codeServerUrl, + required this.tasksCount, + }); +} + +const List mockProjectWorkspaces = [ + ProjectWorkspace( + id: 'nomadcode', + name: 'NomadCode Core', + description: 'AI 병렬 작업 운용을 위한 원레포 백엔드 및 모바일 클라이언트', + status: ProjectStatus.active, + codeServerUrl: 'http://localhost:8080/?folder=/config/workspace/nomadcode', + tasksCount: 3, + ), + ProjectWorkspace( + id: 'iop', + name: 'IOP Bridge', + description: 'RAG 및 MCP 연동을 위한 대규모 자율형 LLM 오케스트레이션 게이트웨이', + status: ProjectStatus.active, + codeServerUrl: 'http://localhost:8080/?folder=/config/workspace/iop', + tasksCount: 1, + ), + ProjectWorkspace( + id: 'proto-socket', + name: 'Proto Socket', + description: '고성능 전이중 RPC 채널 및 웹소켓 메시징 엔진', + status: ProjectStatus.paused, + codeServerUrl: 'http://localhost:8080/?folder=/config/workspace/proto-socket', + tasksCount: 0, + ), +]; diff --git a/apps/mobile/lib/screens/workspace_home_page.dart b/apps/mobile/lib/screens/workspace_home_page.dart new file mode 100644 index 0000000..a605dd8 --- /dev/null +++ b/apps/mobile/lib/screens/workspace_home_page.dart @@ -0,0 +1,442 @@ +import 'package:flutter/material.dart'; +import '../models/project_workspace.dart'; + +class WorkspaceHomePage extends StatefulWidget { + final Future Function(ProjectWorkspace)? onLaunchCodeServer; + + const WorkspaceHomePage({ + super.key, + this.onLaunchCodeServer, + }); + + @override + State createState() => _WorkspaceHomePageState(); +} + +class _WorkspaceHomePageState extends State { + ProjectWorkspace? _selectedProject; + + @override + void initState() { + super.initState(); + // Default to the first active workspace + if (mockProjectWorkspaces.isNotEmpty) { + _selectedProject = mockProjectWorkspaces.first; + } + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final isLargeScreen = MediaQuery.of(context).size.width > 600; + + return Scaffold( + backgroundColor: const Color(0xFF0F0F1A), // Sleek deep dark background + appBar: AppBar( + title: Row( + children: [ + Container( + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + color: Colors.deepPurpleAccent.withValues(alpha: 0.2), + borderRadius: BorderRadius.circular(8), + ), + child: const Icon(Icons.blur_on, color: Colors.deepPurpleAccent, size: 24), + ), + const SizedBox(width: 12), + const Text( + 'NomadCode Console', + style: TextStyle( + fontWeight: FontWeight.bold, + letterSpacing: 0.5, + color: Colors.white, + ), + ), + ], + ), + backgroundColor: const Color(0xFF16162A), + elevation: 0, + actions: [ + IconButton( + icon: const Icon(Icons.refresh, color: Colors.white70), + onPressed: () { + // Refresh state placeholder + }, + ), + ], + ), + body: isLargeScreen ? _buildDualPaneLayout(theme) : _buildSinglePaneLayout(theme), + ); + } + + Widget _buildDualPaneLayout(ThemeData theme) { + return Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // Left Panel: Workspaces list + Expanded( + flex: 2, + child: Container( + decoration: const BoxDecoration( + color: Color(0xFF131326), + border: Border( + right: BorderSide(color: Color(0xFF1E1E38), width: 1), + ), + ), + child: _buildWorkspaceList(), + ), + ), + // Right Panel: Selected Workspace Details + Expanded( + flex: 3, + child: _selectedProject == null + ? const Center( + child: Text( + 'Select a workspace to view details', + style: TextStyle(color: Colors.white38), + ), + ) + : _buildWorkspaceDetails(theme, _selectedProject!), + ), + ], + ); + } + + Widget _buildSinglePaneLayout(ThemeData theme) { + return Column( + children: [ + Expanded( + child: _buildWorkspaceList(), + ), + if (_selectedProject != null) + Container( + padding: const EdgeInsets.all(16), + decoration: const BoxDecoration( + color: Color(0xFF16162A), + border: Border( + top: BorderSide(color: Color(0xFF1E1E38), width: 1), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + Expanded( + child: Text( + _selectedProject!.name, + style: const TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + ), + _buildLaunchButton(_selectedProject!), + ], + ), + ], + ), + ), + ], + ); + } + + Widget _buildWorkspaceList() { + return ListView.builder( + padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 12), + itemCount: mockProjectWorkspaces.length, + itemBuilder: (context, index) { + final project = mockProjectWorkspaces[index]; + final isSelected = _selectedProject?.id == project.id; + + return Card( + color: isSelected ? const Color(0xFF21213D) : const Color(0xFF181830), + elevation: isSelected ? 4 : 1, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + side: BorderSide( + color: isSelected ? Colors.deepPurpleAccent : const Color(0xFF26264A), + width: 1.5, + ), + ), + margin: const EdgeInsets.only(bottom: 12), + child: InkWell( + borderRadius: BorderRadius.circular(12), + onTap: () { + setState(() { + _selectedProject = project; + }); + }, + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + Container( + width: 48, + height: 48, + decoration: BoxDecoration( + color: isSelected + ? Colors.deepPurpleAccent.withValues(alpha: 0.15) + : const Color(0xFF232345), + shape: BoxShape.circle, + ), + child: Icon( + project.status == ProjectStatus.active + ? Icons.code + : Icons.pause_circle_outline, + color: project.status == ProjectStatus.active + ? Colors.cyanAccent + : Colors.amberAccent, + size: 24, + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + project.name, + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + const SizedBox(height: 4), + Text( + project.description, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + color: Colors.white60, + fontSize: 13, + ), + ), + ], + ), + ), + const SizedBox(width: 8), + if (project.tasksCount > 0) + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: Colors.deepPurpleAccent.withValues(alpha: 0.3), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + '${project.tasksCount} Tasks', + style: const TextStyle( + color: Colors.white, + fontSize: 10, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ), + ), + ); + }, + ); + } + + Widget _buildWorkspaceDetails(ThemeData theme, ProjectWorkspace project) { + return SingleChildScrollView( + padding: const EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + project.name, + style: const TextStyle( + color: Colors.white, + fontSize: 28, + fontWeight: FontWeight.bold, + letterSpacing: -0.5, + ), + ), + const SizedBox(height: 8), + Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + decoration: BoxDecoration( + color: project.status == ProjectStatus.active + ? Colors.cyanAccent.withValues(alpha: 0.15) + : Colors.amberAccent.withValues(alpha: 0.15), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + project.status.name.toUpperCase(), + style: TextStyle( + color: project.status == ProjectStatus.active + ? Colors.cyanAccent + : Colors.amberAccent, + fontSize: 11, + fontWeight: FontWeight.bold, + letterSpacing: 1.0, + ), + ), + ), + ], + ), + ), + _buildLaunchButton(project), + ], + ), + const SizedBox(height: 24), + const Divider(color: Color(0xFF26264A)), + const SizedBox(height: 16), + const Text( + 'DESCRIPTION', + style: TextStyle( + color: Colors.white38, + fontSize: 12, + fontWeight: FontWeight.bold, + letterSpacing: 1.0, + ), + ), + const SizedBox(height: 8), + Text( + project.description, + style: const TextStyle( + color: Colors.white70, + fontSize: 15, + height: 1.5, + ), + ), + const SizedBox(height: 32), + const Text( + 'ACTIVE TASKS', + style: TextStyle( + color: Colors.white38, + fontSize: 12, + fontWeight: FontWeight.bold, + letterSpacing: 1.0, + ), + ), + const SizedBox(height: 16), + if (project.tasksCount == 0) + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: const Color(0xFF14142B), + borderRadius: BorderRadius.circular(12), + border: Border.all(color: const Color(0xFF222244)), + ), + child: const Row( + children: [ + Icon(Icons.check_circle_outline, color: Colors.greenAccent), + SizedBox(width: 16), + Text( + 'No active tasks. All tasks are idle.', + style: TextStyle(color: Colors.white54, fontSize: 14), + ), + ], + ), + ) + else + Column( + children: List.generate( + project.tasksCount, + (i) => Container( + margin: const EdgeInsets.only(bottom: 12), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: const Color(0xFF181835), + borderRadius: BorderRadius.circular(12), + border: Border.all(color: const Color(0xFF2B2B52)), + ), + child: Row( + children: [ + const Icon(Icons.sync, color: Colors.deepPurpleAccent), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Task #${1000 + i + (project.name.hashCode % 100)}', + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 14, + ), + ), + const SizedBox(height: 4), + const Text( + 'Running workflow core optimizations...', + style: TextStyle(color: Colors.white54, fontSize: 12), + ), + ], + ), + ), + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: Colors.cyanAccent.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(6), + ), + child: const Text( + 'running', + style: TextStyle( + color: Colors.cyanAccent, + fontSize: 11, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ), + ), + ), + ], + ), + ); + } + + Widget _buildLaunchButton(ProjectWorkspace project) { + return ElevatedButton.icon( + style: ElevatedButton.styleFrom( + backgroundColor: Colors.deepPurpleAccent, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 6, + shadowColor: Colors.deepPurpleAccent.withValues(alpha: 0.4), + ), + icon: const Icon(Icons.rocket_launch, size: 20), + label: const Text( + 'Open Workspace', + style: TextStyle( + fontWeight: FontWeight.bold, + letterSpacing: 0.5, + ), + ), + onPressed: () async { + if (widget.onLaunchCodeServer != null) { + final success = await widget.onLaunchCodeServer!(project); + if (success && mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Launching ${project.name} workspace...'), + behavior: SnackBarBehavior.floating, + ), + ); + } + } + }, + ); + } +} diff --git a/apps/mobile/lib/services/workspace_launcher.dart b/apps/mobile/lib/services/workspace_launcher.dart new file mode 100644 index 0000000..acefc34 --- /dev/null +++ b/apps/mobile/lib/services/workspace_launcher.dart @@ -0,0 +1,12 @@ +import 'package:url_launcher/url_launcher.dart'; +import '../models/project_workspace.dart'; + +class WorkspaceLauncher { + Future openCodeServer(ProjectWorkspace project) async { + final uri = Uri.parse(project.codeServerUrl); + if (await canLaunchUrl(uri)) { + return launchUrl(uri, mode: LaunchMode.externalApplication); + } + return false; + } +} diff --git a/apps/mobile/linux/flutter/generated_plugin_registrant.cc b/apps/mobile/linux/flutter/generated_plugin_registrant.cc index e71a16d..f6f23bf 100644 --- a/apps/mobile/linux/flutter/generated_plugin_registrant.cc +++ b/apps/mobile/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,10 @@ #include "generated_plugin_registrant.h" +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); } diff --git a/apps/mobile/linux/flutter/generated_plugins.cmake b/apps/mobile/linux/flutter/generated_plugins.cmake index 2e1de87..f16b4c3 100644 --- a/apps/mobile/linux/flutter/generated_plugins.cmake +++ b/apps/mobile/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + url_launcher_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/apps/mobile/macos/Flutter/GeneratedPluginRegistrant.swift b/apps/mobile/macos/Flutter/GeneratedPluginRegistrant.swift index 29262c1..af5d95a 100644 --- a/apps/mobile/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/apps/mobile/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,9 +8,11 @@ import Foundation import firebase_core import firebase_messaging import flutter_local_notifications +import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin")) FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/apps/mobile/pubspec.lock b/apps/mobile/pubspec.lock index 5046551..d7a350e 100644 --- a/apps/mobile/pubspec.lock +++ b/apps/mobile/pubspec.lock @@ -349,6 +349,70 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 + url: "https://pub.dev" + source: hosted + version: "6.3.2" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "17bc677f0b301615530dd1d67e0a9828cafa2d0b6b6eae4cd3679b7eac4a273c" + url: "https://pub.dev" + source: hosted + version: "6.3.30" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0" + url: "https://pub.dev" + source: hosted + version: "6.4.1" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" + url: "https://pub.dev" + source: hosted + version: "3.2.5" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34" + url: "https://pub.dev" + source: hosted + version: "2.4.3" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" + url: "https://pub.dev" + source: hosted + version: "3.1.5" vector_math: dependency: transitive description: @@ -391,4 +455,4 @@ packages: version: "6.6.1" sdks: dart: ">=3.11.0 <4.0.0" - flutter: ">=3.22.0" + flutter: ">=3.38.0" diff --git a/apps/mobile/pubspec.yaml b/apps/mobile/pubspec.yaml index ac7c1b2..eaf627a 100644 --- a/apps/mobile/pubspec.yaml +++ b/apps/mobile/pubspec.yaml @@ -45,6 +45,7 @@ dependencies: # Local notifications (foreground display) flutter_local_notifications: ^18.0.1 + url_launcher: ^6.3.2 dev_dependencies: flutter_test: diff --git a/apps/mobile/test/widget_test.dart b/apps/mobile/test/widget_test.dart index 84cea8f..dc730cc 100644 --- a/apps/mobile/test/widget_test.dart +++ b/apps/mobile/test/widget_test.dart @@ -1,18 +1,39 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility in the flutter_test package. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - +import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; - import 'package:nomadcode_app/main.dart'; +import 'package:nomadcode_app/screens/workspace_home_page.dart'; +import 'package:nomadcode_app/models/project_workspace.dart'; void main() { - testWidgets('App renders project name', (WidgetTester tester) async { + testWidgets('App renders console dashboard title', (WidgetTester tester) async { await tester.pumpWidget(const MyApp()); - expect(find.text('nomadcode-app'), findsOneWidget); + expect(find.text('NomadCode Console'), findsOneWidget); + expect(find.text('NomadCode Core'), findsNWidgets(2)); + expect(find.text('IOP Bridge'), findsOneWidget); + }); + + testWidgets('WorkspaceHomePage triggers launcher callback on button tap', (WidgetTester tester) async { + ProjectWorkspace? launchedProject; + + await tester.pumpWidget( + MaterialApp( + home: WorkspaceHomePage( + onLaunchCodeServer: (project) async { + launchedProject = project; + return true; + }, + ), + ), + ); + + final buttonFinder = find.widgetWithText(ElevatedButton, 'Open Workspace'); + expect(buttonFinder, findsOneWidget); + + await tester.tap(buttonFinder); + await tester.pumpAndSettle(); + + expect(launchedProject, isNotNull); + expect(launchedProject!.id, equals('nomadcode')); }); } diff --git a/apps/mobile/windows/flutter/generated_plugin_registrant.cc b/apps/mobile/windows/flutter/generated_plugin_registrant.cc index 1a82e7d..ec8e8d4 100644 --- a/apps/mobile/windows/flutter/generated_plugin_registrant.cc +++ b/apps/mobile/windows/flutter/generated_plugin_registrant.cc @@ -7,8 +7,11 @@ #include "generated_plugin_registrant.h" #include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { FirebaseCorePluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("FirebaseCorePluginCApi")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/apps/mobile/windows/flutter/generated_plugins.cmake b/apps/mobile/windows/flutter/generated_plugins.cmake index fa8a39b..02d26c3 100644 --- a/apps/mobile/windows/flutter/generated_plugins.cmake +++ b/apps/mobile/windows/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST firebase_core + url_launcher_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/apps/web/.env.example b/apps/web/.env.example deleted file mode 100644 index eaec45b..0000000 --- a/apps/web/.env.example +++ /dev/null @@ -1 +0,0 @@ -VITE_NOMADCODE_API_BASE_URL=http://localhost:8080 diff --git a/apps/web/.gitignore b/apps/web/.gitignore deleted file mode 100644 index b6f8c6a..0000000 --- a/apps/web/.gitignore +++ /dev/null @@ -1,13 +0,0 @@ -node_modules/ -dist/ -.vite/ -*.tsbuildinfo -.env -.env.local -.env.*.local -npm-debug.log* -pnpm-debug.log* -yarn-debug.log* - -# Agent-Ops Private Rules -agent-ops/rules/private/ diff --git a/apps/web/README.md b/apps/web/README.md deleted file mode 100644 index 5fa9c52..0000000 --- a/apps/web/README.md +++ /dev/null @@ -1,49 +0,0 @@ -# nomadcode-web - -`nomadcode-web` is the initial React web frontend scaffold for NomadCode Server. It gives Agent Chat, Project Manage, and Settings pages a small but expandable layout. - -## Stack - -- React -- TypeScript -- Vite -- TanStack Router -- TanStack Query -- Zustand -- Plain CSS - -## Run - -```bash -npm install -npm run dev -``` - -## Environment - -```bash -VITE_NOMADCODE_API_BASE_URL=http://localhost:8080 -``` - -The default matches the current `nomadcode-core` local server. - -## Pages - -- `/agent` -- `/projects` -- `/settings` - -## Mock and Stub Areas - -- Agent messages are local mock state. -- Project actions log or open placeholder links. -- Settings integrations show mock configured/status values. -- Plane, Mattermost, IOP, and Agent Integrator calls are not implemented. -- WebSocket and proto-socket runtime flows are not implemented. - -## Next Steps - -- Connect task creation/listing to `nomadcode-core` `/api/tasks`. -- Add server-backed settings persistence. -- Add real project metadata loading. -- Add authenticated API access when Core auth is finalized. diff --git a/apps/web/index.html b/apps/web/index.html deleted file mode 100644 index 2f1c4db..0000000 --- a/apps/web/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - NomadCode Web - - -
- - - diff --git a/apps/web/package-lock.json b/apps/web/package-lock.json deleted file mode 100644 index f61dd11..0000000 --- a/apps/web/package-lock.json +++ /dev/null @@ -1,1174 +0,0 @@ -{ - "name": "nomadcode-web", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "nomadcode-web", - "version": "0.1.0", - "dependencies": { - "@tanstack/react-query": "^5.100.11", - "@tanstack/react-router": "^1.170.6", - "lucide-react": "^1.16.0", - "react": "^19.2.6", - "react-dom": "^19.2.6", - "zustand": "^5.0.13" - }, - "devDependencies": { - "@types/react": "^19.2.15", - "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^6.0.2", - "typescript": "^6.0.3", - "vite": "^8.0.13" - }, - "engines": { - "node": ">=20.9.0", - "npm": ">=10" - } - }, - "node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", - "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" - } - }, - "node_modules/@oxc-project/types": { - "version": "0.130.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.130.0.tgz", - "integrity": "sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.1.tgz", - "integrity": "sha512-fJI3I0r3C3Oj/zdBCpaCmBRZYf07xpaq4yCfDDoSFm+beWNzbIl26puW8RraUdugoJw/95zerNOn6jasAhzSmg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.1.tgz", - "integrity": "sha512-cKnAhWEsV7TPcA/5EAteDp6KcJZBQ2G+BqE7zayMMi7kMvwRsbv7WT9aOnn0WNl4SKEIf43vjS31iUPu80nzXg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.1.tgz", - "integrity": "sha512-YKrVwQjIRBPo+5G/u03wGjbdy4q7pyzCe93DK9VJ7zkVmeg8LJ7GbgsiHWdR4xSoe4CAXRD7Bcjgbtr64bkXNg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.1.tgz", - "integrity": "sha512-z/oBsREo46SsFqBwYtFe0kpJeBijAT48O/WXLI4suiCLBkr03RTtTJMCzSdDd2znlh8VJizL09XVkQgk8IZonw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.1.tgz", - "integrity": "sha512-ik8q7GM11zxvYxFc2PeDcT6TBvhCQMaUxfph/M5l9sKuTs/Sjg3L+Byw0F7w0ZVLBZmx30P+gG0ECzzN+MFcmQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.1.tgz", - "integrity": "sha512-QoSx2EkyrrdZ6kcyE8stqZ62t0Yra8Fs5ia9lOxJrh6TMQJK7gQKmscdTHf7pOXKREKrVwOtJcQG3qVSfc866A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.1.tgz", - "integrity": "sha512-uwNwFpwKeNiZawfAWBgg0VIztPTV3ihhh1vV334h9ivnNLorxnQMU6Fz8wG1Zb4Qh9LC1/MkcyT3YlDXG3Rsgg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.1.tgz", - "integrity": "sha512-zY1bul7OWr7DFBiJ++wofXvnr8B45ce3QsQUhKrIhXsygAh7bTkwyeM1bi1a2g5C/yC/N8TZyGDEoMfm/l9mpg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.1.tgz", - "integrity": "sha512-0frlsT/f4Ft6I7SMESTKnF3cZsdicQn1dCMkF/jT9wDLE+gGoiQfv1nmT9e+s7s/fekvvy6tZM2jHvI2tkbJDQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.1.tgz", - "integrity": "sha512-XABVmGp9Tg0WspTVvwduTc4fpqy6JnAUrSQe6OuyqD/03nI7r0O9OWUkMIwFrjKAIqolvqoA4ZrJppgwE0Gxmw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.1.tgz", - "integrity": "sha512-bV4fzswuzVcKD90o/VM6QqKxnxlDq0g2BISDLNVmxrnhpv1DDbyPhCIjYfvzYLV+MvkKKnQt2Q6AO86SEBULUQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.1.tgz", - "integrity": "sha512-/Mh0Zhq3OP7fVs0kcQHZP6lZEthMGTaSf8UBQYSFEZDWGXXlEC+nJ6EqenaK2t4LBXMe3A+K/G2BVXXdtOr4PQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.1.tgz", - "integrity": "sha512-+1xc9X45l8ufsBAm6Gjvx2qDRIY9lTVt0cgWNcJ+1gdhXvkbxePA60yRTwSTuXL09CMhyJmjpV7E3NoyxbqFQQ==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.1.tgz", - "integrity": "sha512-1D+UqZdfnuR+Jy1GgMJwi85bD40H21uNmOPRWQhw4oRSuolZ/B5rixZ45DK2KXOTCvmVCecauWgEhbw8bI7tOw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.1.tgz", - "integrity": "sha512-INAycaWuhlOK3wk4mRHGsdgwYWmd9cChdPdE9bwWmy6rn9VqVNYNFGhOdXrofXUxwHIncSiPNb8tNm8knDVIeQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", - "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tanstack/history": { - "version": "1.162.0", - "resolved": "https://registry.npmjs.org/@tanstack/history/-/history-1.162.0.tgz", - "integrity": "sha512-79pf/RkhteYZTRgcR4F9kbk84P2N8rugQJswxfIqovlbRiT3yI7eBE+5QorIrZaOKktsgzRlXh1l/du/xpl4iA==", - "license": "MIT", - "engines": { - "node": ">=20.19" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - } - }, - "node_modules/@tanstack/query-core": { - "version": "5.100.11", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.100.11.tgz", - "integrity": "sha512-lmE0994apShXPj8CUxgx4ch5yUJhE9k/+tVwihBvPOyerACWdBocfFg24t8+0RhtlTd7tEgchDkhlCxNssvDxw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - } - }, - "node_modules/@tanstack/react-query": { - "version": "5.100.11", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.100.11.tgz", - "integrity": "sha512-J0f9s5x3LE1450nNNfYx+e/n0DMa0uOBdFJUy5r0RvmsXd4nB/n0rbHtHI1vYXhikNFan+wf51p6Tmp4c8ucrg==", - "license": "MIT", - "dependencies": { - "@tanstack/query-core": "5.100.11" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "react": "^18 || ^19" - } - }, - "node_modules/@tanstack/react-router": { - "version": "1.170.6", - "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.170.6.tgz", - "integrity": "sha512-tGQkOjcMESBbfw+iz9zE/ivcuw4D2zdhW8PA4wMmpOyA2bLiqpc6bg5MnJPxamdXoO3GZBiHYOOoEwi5qxpPgA==", - "license": "MIT", - "dependencies": { - "@tanstack/history": "1.162.0", - "@tanstack/react-store": "^0.9.3", - "@tanstack/router-core": "1.171.4", - "isbot": "^5.1.22" - }, - "engines": { - "node": ">=20.19" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "react": ">=18.0.0 || >=19.0.0", - "react-dom": ">=18.0.0 || >=19.0.0" - } - }, - "node_modules/@tanstack/react-store": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/@tanstack/react-store/-/react-store-0.9.3.tgz", - "integrity": "sha512-y2iHd/N9OkoQbFJLUX1T9vbc2O9tjH0pQRgTcx1/Nz4IlwLvkgpuglXUx+mXt0g5ZDFrEeDnONPqkbfxXJKwRg==", - "license": "MIT", - "dependencies": { - "@tanstack/store": "0.9.3", - "use-sync-external-store": "^1.6.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/@tanstack/router-core": { - "version": "1.171.4", - "resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.171.4.tgz", - "integrity": "sha512-LU9YuVdgaP+h4MEXRvokyjIEelulylgsromHMfYwVfgo1PF9oJP3NHyy7qtjxGLJq6zoZMCfoa1frDJlPo7I8g==", - "license": "MIT", - "dependencies": { - "@tanstack/history": "1.162.0", - "cookie-es": "^3.0.0", - "seroval": "^1.5.4", - "seroval-plugins": "^1.5.4" - }, - "engines": { - "node": ">=20.19" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - } - }, - "node_modules/@tanstack/store": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.9.3.tgz", - "integrity": "sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - } - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", - "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/react": { - "version": "19.2.15", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.15.tgz", - "integrity": "sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "csstype": "^3.2.2" - } - }, - "node_modules/@types/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "^19.2.0" - } - }, - "node_modules/@vitejs/plugin-react": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.2.tgz", - "integrity": "sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rolldown/pluginutils": "^1.0.0" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "peerDependencies": { - "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", - "babel-plugin-react-compiler": "^1.0.0", - "vite": "^8.0.0" - }, - "peerDependenciesMeta": { - "@rolldown/plugin-babel": { - "optional": true - }, - "babel-plugin-react-compiler": { - "optional": true - } - } - }, - "node_modules/cookie-es": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-3.1.1.tgz", - "integrity": "sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==", - "license": "MIT" - }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/isbot": { - "version": "5.1.40", - "resolved": "https://registry.npmjs.org/isbot/-/isbot-5.1.40.tgz", - "integrity": "sha512-yNeeynhhtIVRBk12tBV4eHNxwB42HzR4Q3Ea7vCOiJhImGaAIdIMrbJtacQlBizGLjUPw+akkFI5Dn9T70XoVQ==", - "license": "Unlicense", - "engines": { - "node": ">=18" - } - }, - "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lucide-react": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.16.0.tgz", - "integrity": "sha512-dYwyPzb4MEKpGUmNYk3WKWPnMrHs3FKM+q94kAnJrcDIqqn1hq2xY8scaS2ovsOCM5D51ey2gaRG3PBb1vgoYQ==", - "license": "ISC", - "peerDependencies": { - "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.15", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", - "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.12", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/react": { - "version": "19.2.6", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz", - "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "19.2.6", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz", - "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==", - "license": "MIT", - "dependencies": { - "scheduler": "^0.27.0" - }, - "peerDependencies": { - "react": "^19.2.6" - } - }, - "node_modules/rolldown": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.1.tgz", - "integrity": "sha512-X0KQHljNnEkWNqqiz9zJrGunh1B0HgOxLXvnFpCOcadzcy5qohZ3tqMEUg00vncoRovXuK3ZqCT9KnnKzoInFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@oxc-project/types": "=0.130.0", - "@rolldown/pluginutils": "^1.0.0" - }, - "bin": { - "rolldown": "bin/cli.mjs" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.1", - "@rolldown/binding-darwin-arm64": "1.0.1", - "@rolldown/binding-darwin-x64": "1.0.1", - "@rolldown/binding-freebsd-x64": "1.0.1", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.1", - "@rolldown/binding-linux-arm64-gnu": "1.0.1", - "@rolldown/binding-linux-arm64-musl": "1.0.1", - "@rolldown/binding-linux-ppc64-gnu": "1.0.1", - "@rolldown/binding-linux-s390x-gnu": "1.0.1", - "@rolldown/binding-linux-x64-gnu": "1.0.1", - "@rolldown/binding-linux-x64-musl": "1.0.1", - "@rolldown/binding-openharmony-arm64": "1.0.1", - "@rolldown/binding-wasm32-wasi": "1.0.1", - "@rolldown/binding-win32-arm64-msvc": "1.0.1", - "@rolldown/binding-win32-x64-msvc": "1.0.1" - } - }, - "node_modules/scheduler": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", - "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "license": "MIT" - }, - "node_modules/seroval": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.5.4.tgz", - "integrity": "sha512-46uFvgrXTVxZcUorgSSRZ4y+ieqLLQRMlG4bnCZKW3qI6BZm7Rg4ntMW4p1mILEEBZWrFlcpp0AyIIlM6jD9iw==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/seroval-plugins": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.5.4.tgz", - "integrity": "sha512-S0xQPhUTefAhNvNWFg0c1J8qJArHt5KdtJ/cFAofo06KD1MVSeFWyl4iiu+ApDIuw0WhjpOfCdgConOfAnLgkw==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "seroval": "^1.0" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, - "node_modules/typescript": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", - "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/use-sync-external-store": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", - "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", - "license": "MIT", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/vite": { - "version": "8.0.13", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.13.tgz", - "integrity": "sha512-MFtjBYgzmSxmgA4RAfjIyXWpGe1oALnjgUTzzV7QLx/TKxCzjtMH6Fd9/eVK+5Fg1qNoz5VAwsmMs/NofrmJvw==", - "dev": true, - "license": "MIT", - "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.14", - "rolldown": "1.0.1", - "tinyglobby": "^0.2.16" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.18", - "esbuild": "^0.27.0 || ^0.28.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "@vitejs/devtools": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/zustand": { - "version": "5.0.13", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.13.tgz", - "integrity": "sha512-efI2tVaVQPqtOh114loML/Z80Y4NP3yc+Ff0fYiZJPauNeWZeIp/bRFD7I9bfmCOYBh/PHxlglQ9+wvlwnPikQ==", - "license": "MIT", - "engines": { - "node": ">=12.20.0" - }, - "peerDependencies": { - "@types/react": ">=18.0.0", - "immer": ">=9.0.6", - "react": ">=18.0.0", - "use-sync-external-store": ">=1.2.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "immer": { - "optional": true - }, - "react": { - "optional": true - }, - "use-sync-external-store": { - "optional": true - } - } - } - } -} diff --git a/apps/web/package.json b/apps/web/package.json deleted file mode 100644 index 3327300..0000000 --- a/apps/web/package.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "nomadcode-web", - "version": "0.1.0", - "private": true, - "type": "module", - "scripts": { - "dev": "vite", - "build": "tsc -b && vite build", - "preview": "vite preview", - "check": "tsc --noEmit", - "verify": "npm run check && npm run build" - }, - "engines": { - "node": ">=20.9.0", - "npm": ">=10" - }, - "dependencies": { - "@tanstack/react-query": "^5.100.11", - "@tanstack/react-router": "^1.170.6", - "lucide-react": "^1.16.0", - "react": "^19.2.6", - "react-dom": "^19.2.6", - "zustand": "^5.0.13" - }, - "devDependencies": { - "@types/react": "^19.2.15", - "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^6.0.2", - "typescript": "^6.0.3", - "vite": "^8.0.13" - } -} diff --git a/apps/web/src/api/client.ts b/apps/web/src/api/client.ts deleted file mode 100644 index 578dc1b..0000000 --- a/apps/web/src/api/client.ts +++ /dev/null @@ -1,36 +0,0 @@ -const DEFAULT_API_BASE_URL = "http://localhost:8080"; - -export function getApiBaseUrl(): string { - const configured = import.meta.env.VITE_NOMADCODE_API_BASE_URL; - return (configured || DEFAULT_API_BASE_URL).replace(/\/+$/, ""); -} - -export function buildApiUrl(path: string): string { - const normalizedPath = path.startsWith("/") ? path : `/${path}`; - return `${getApiBaseUrl()}${normalizedPath}`; -} - -export async function apiFetch(path: string, init?: RequestInit): Promise { - const response = await fetch(buildApiUrl(path), { - headers: { - "Content-Type": "application/json", - ...init?.headers, - }, - ...init, - }); - - if (!response.ok) { - throw new Error(`NomadCode API request failed: ${response.status}`); - } - - return response.json() as Promise; -} - -export async function getServerHealth(): Promise<{ ok: boolean; label: string }> { - try { - const response = await fetch(buildApiUrl("/healthz")); - return { ok: response.ok, label: response.ok ? "ok" : "unavailable" }; - } catch { - return { ok: false, label: "offline" }; - } -} diff --git a/apps/web/src/api/projects.ts b/apps/web/src/api/projects.ts deleted file mode 100644 index 39c804e..0000000 --- a/apps/web/src/api/projects.ts +++ /dev/null @@ -1,88 +0,0 @@ -export type ProjectStatus = "ready" | "active" | "paused" | "archived"; - -export interface Project { - id: string; - name: string; - description: string; - workspacePath: string; - codeServerUrl: string; - repoUrl: string; - planeProject: string; - outlineDoc: string; - defaultAgent: string; - status: ProjectStatus; - lastOpenedAt: string; -} - -export const mockProjects: Project[] = [ - { - id: "nomadcode-web", - name: "nomadcode-web", - description: "React web frontend for NomadCode Server.", - workspacePath: "/config/workspace/nomadcode-web", - codeServerUrl: "http://localhost:8081/?folder=/config/workspace/nomadcode-web", - repoUrl: "https://github.com/nomadcode/nomadcode-web", - planeProject: "NC-WEB", - outlineDoc: "NomadCode Web UI", - defaultAgent: "codex-local", - status: "active", - lastOpenedAt: "2026-05-21T00:00:00Z", - }, - { - id: "nomadcode-core", - name: "nomadcode-core", - description: "Go server for task APIs, workflow state, and async agent jobs.", - workspacePath: "/config/workspace/nomadcode-core", - codeServerUrl: "http://localhost:8081/?folder=/config/workspace/nomadcode-core", - repoUrl: "https://github.com/nomadcode/nomadcode-core", - planeProject: "NC-CORE", - outlineDoc: "NomadCode Core Server", - defaultAgent: "codex-local", - status: "ready", - lastOpenedAt: "2026-05-20T14:30:00Z", - }, - { - id: "iop", - name: "iop", - description: "Inference Operations Platform skeleton for edge, node, and control plane flows.", - workspacePath: "/config/workspace/iop", - codeServerUrl: "http://localhost:8081/?folder=/config/workspace/iop", - repoUrl: "https://github.com/nomadcode/iop", - planeProject: "IOP", - outlineDoc: "IOP Control Plane", - defaultAgent: "codex-local", - status: "ready", - lastOpenedAt: "2026-05-19T09:10:00Z", - }, - { - id: "proto-socket", - name: "proto-socket", - description: "Protocol buffer socket library with TypeScript, Go, Dart, Kotlin, and Python implementations.", - workspacePath: "/config/workspace/proto-socket", - codeServerUrl: "http://localhost:8081/?folder=/config/workspace/proto-socket", - repoUrl: "https://github.com/nomadcode/proto-socket", - planeProject: "PROTO", - outlineDoc: "Proto Socket Wire Protocol", - defaultAgent: "codex-local", - status: "paused", - lastOpenedAt: "2026-05-18T16:45:00Z", - }, -]; - -export async function listProjectsPlaceholder(): Promise { - return mockProjects; -} - -export function openWorkspacePlaceholder(project: Project): void { - console.log("[projects] open workspace placeholder", project.workspacePath); - window.open(project.codeServerUrl, "_blank", "noopener,noreferrer"); -} - -export function openCodeServerPlaceholder(project: Project): void { - console.log("[projects] open code-server placeholder", project.codeServerUrl); - window.open(project.codeServerUrl, "_blank", "noopener,noreferrer"); -} - -export function viewRecentJobsPlaceholder(project: Project): void { - console.log("[projects] view recent jobs placeholder", project.id); -} diff --git a/apps/web/src/api/settings.ts b/apps/web/src/api/settings.ts deleted file mode 100644 index 379b717..0000000 --- a/apps/web/src/api/settings.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { getApiBaseUrl, getServerHealth } from "./client"; - -export type IntegrationStatusLabel = "connected" | "offline" | "mock" | "unknown"; - -export interface IntegrationSettings { - key: string; - name: string; - enabled: boolean; - endpointLabel: string; - tokenConfigured?: boolean; - status: IntegrationStatusLabel; -} - -export interface SettingsSnapshot { - server: { - apiBaseUrl: string; - connectionStatus: IntegrationStatusLabel; - }; - integrations: IntegrationSettings[]; -} - -export async function getSettingsSnapshot(): Promise { - const health = await getServerHealth(); - - return { - server: { - apiBaseUrl: getApiBaseUrl(), - connectionStatus: health.ok ? "connected" : "offline", - }, - integrations: [ - { - key: "mattermost", - name: "Mattermost", - enabled: true, - endpointLabel: "https://mattermost.example.local", - tokenConfigured: true, - status: "mock", - }, - { - key: "plane", - name: "Plane", - enabled: true, - endpointLabel: "https://plane.example.local", - tokenConfigured: false, - status: "mock", - }, - { - key: "iop", - name: "IOP", - enabled: true, - endpointLabel: "http://localhost:9080", - status: "mock", - }, - { - key: "agent-integrator", - name: "Agent Integrator", - enabled: false, - endpointLabel: "http://localhost:19080", - status: "unknown", - }, - ], - }; -} - -export async function testIntegrationPlaceholder(key: string): Promise<{ ok: boolean; label: string }> { - console.log("[settings] test integration placeholder", key); - return { ok: true, label: "mock" }; -} diff --git a/apps/web/src/api/tasks.ts b/apps/web/src/api/tasks.ts deleted file mode 100644 index 04b1dc3..0000000 --- a/apps/web/src/api/tasks.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { apiFetch } from "./client"; - -export type TaskStatus = "pending" | "queued" | "running" | "completed" | "failed" | "canceled"; - -export interface NomadTask { - id: string; - title: string; - source: string; - status: TaskStatus; - payload?: unknown; - result?: unknown; - error?: string | null; - createdAt?: string; - updatedAt?: string; -} - -export interface CreateTaskInput { - title: string; - source: string; - payload: unknown; -} - -export async function listTasks(limit = 20): Promise { - return apiFetch(`/api/tasks?limit=${limit}`); -} - -export async function createTaskPlaceholder(input: CreateTaskInput): Promise<{ id: string; status: TaskStatus }> { - console.log("[tasks] create task placeholder", input); - return { - id: `task-${Date.now()}`, - status: "pending", - }; -} - -export async function enqueueTaskPlaceholder(taskId: string): Promise<{ id: string; status: TaskStatus }> { - console.log("[tasks] enqueue task placeholder", taskId); - return { - id: taskId, - status: "queued", - }; -} diff --git a/apps/web/src/app/App.tsx b/apps/web/src/app/App.tsx deleted file mode 100644 index 3af0f2f..0000000 --- a/apps/web/src/app/App.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { RouterProvider } from "@tanstack/react-router"; -import { useMemo } from "react"; - -import { router } from "./router"; - -export function App() { - const queryClient = useMemo( - () => - new QueryClient({ - defaultOptions: { - queries: { - refetchOnWindowFocus: false, - retry: 1, - }, - }, - }), - [], - ); - - return ( - - - - ); -} diff --git a/apps/web/src/app/router.tsx b/apps/web/src/app/router.tsx deleted file mode 100644 index fe72db1..0000000 --- a/apps/web/src/app/router.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { createRootRoute, createRoute, createRouter, Navigate } from "@tanstack/react-router"; - -import { AppShell } from "../layout/AppShell"; -import { AgentPage } from "../routes/AgentPage"; -import { ProjectsPage } from "../routes/ProjectsPage"; -import { SettingsPage } from "../routes/SettingsPage"; - -const rootRoute = createRootRoute({ - component: AppShell, -}); - -const indexRoute = createRoute({ - getParentRoute: () => rootRoute, - path: "/", - component: () => , -}); - -const agentRoute = createRoute({ - getParentRoute: () => rootRoute, - path: "/agent", - component: AgentPage, -}); - -const projectsRoute = createRoute({ - getParentRoute: () => rootRoute, - path: "/projects", - component: ProjectsPage, -}); - -const settingsRoute = createRoute({ - getParentRoute: () => rootRoute, - path: "/settings", - component: SettingsPage, -}); - -const routeTree = rootRoute.addChildren([indexRoute, agentRoute, projectsRoute, settingsRoute]); - -export const router = createRouter({ - routeTree, - defaultPreload: "intent", -}); - -declare module "@tanstack/react-router" { - interface Register { - router: typeof router; - } -} diff --git a/apps/web/src/components/agent/AgentContextCard.tsx b/apps/web/src/components/agent/AgentContextCard.tsx deleted file mode 100644 index e5aada1..0000000 --- a/apps/web/src/components/agent/AgentContextCard.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import type { AgentContext } from "../../stores/agentStore"; - -interface AgentContextCardProps { - context: AgentContext; -} - -export function AgentContextCard({ context }: AgentContextCardProps) { - return ( -
-
- Page - {context.currentPage || "unknown"} -
-
- Project - {context.selectedProject || "none"} -
-
- Workspace - {context.activeWorkspace || "none"} -
-
- ); -} diff --git a/apps/web/src/components/agent/AgentInput.tsx b/apps/web/src/components/agent/AgentInput.tsx deleted file mode 100644 index cff789f..0000000 --- a/apps/web/src/components/agent/AgentInput.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { Send } from "lucide-react"; -import { FormEvent, useState } from "react"; - -import { Button } from "../common/Button"; - -interface AgentInputProps { - placeholder?: string; - onSend: (message: string) => void; -} - -export function AgentInput({ placeholder = "Ask the agent to work on this context", onSend }: AgentInputProps) { - const [message, setMessage] = useState(""); - - function handleSubmit(event: FormEvent) { - event.preventDefault(); - const nextMessage = message.trim(); - if (!nextMessage) { - return; - } - onSend(nextMessage); - setMessage(""); - } - - return ( -
-