feat: update workbench shell, mobile rules, and tests
This commit is contained in:
parent
f769dd5f3c
commit
d4ff947dcd
5 changed files with 78 additions and 17 deletions
|
|
@ -7,7 +7,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을 포함합니다.
|
- `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/client`는 NomadCode 제품 UI의 기준 Flutter 앱이며 모바일/데스크톱/선택적 web target, Firebase Messaging, local notifications, HTTP client 의존성을 포함합니다. 현재 top titlebar, 우측 activity rail, 우측 Agent dock panel을 가진 workbench shell scaffold가 들어가 있고, Agent dock은 공통 `agent_shell` package를 사용합니다.
|
- `apps/client`는 NomadCode 제품 UI의 기준 Flutter 앱이며 모바일/데스크톱/선택적 web target, Firebase Messaging, local notifications, HTTP client 의존성을 포함합니다. 현재 top titlebar, 우측 activity rail, 우측 Agent dock panel을 가진 workbench shell scaffold가 들어가 있고, Agent dock은 공통 `agent_shell` package를 사용합니다. IOP는 우측 rail 하단 아이콘으로 분리되어 있으며, IOP package/widget이 없을 때도 빌드가 깨지지 않는 선택적 `iopContent` slot으로 조립합니다.
|
||||||
- `apps/web`은 이전 React/Vite 운영 콘솔 scaffold였으며, Flutter-first 통합에 따라 완전히 폐기 및 제거되었습니다.
|
- `apps/web`은 이전 React/Vite 운영 콘솔 scaffold였으며, Flutter-first 통합에 따라 완전히 폐기 및 제거되었습니다.
|
||||||
- `packages/contracts`는 향후 OpenAPI, protobuf, generated client, fixture, compatibility asset을 담을 공유 계약 공간입니다.
|
- `packages/contracts`는 향후 OpenAPI, protobuf, generated client, fixture, compatibility asset을 담을 공유 계약 공간입니다.
|
||||||
- 활성 계획과 Milestone 상태는 `agent-roadmap/`에서 관리합니다. README에는 현재 작업 상태를 복사하지 않고 진입 문서만 연결합니다.
|
- 활성 계획과 Milestone 상태는 `agent-roadmap/`에서 관리합니다. README에는 현재 작업 상태를 복사하지 않고 진입 문서만 연결합니다.
|
||||||
|
|
@ -20,7 +20,7 @@ NomadCode는 제품과 워크플로우 계층을 소유합니다.
|
||||||
- Plane/Jira 스타일 work item intake, provider projection, comment, status update
|
- Plane/Jira 스타일 work item intake, provider projection, comment, status update
|
||||||
- 사용자에게 노출되는 file change, diff, branch, commit, PR 흐름
|
- 사용자에게 노출되는 file change, diff, branch, commit, PR 흐름
|
||||||
- 모바일/데스크톱/선택적 web target의 Flutter project management surface
|
- 모바일/데스크톱/선택적 web target의 Flutter project management surface
|
||||||
- top titlebar, 우측 activity rail, 중앙 content 전환, 우측 Agent dock으로 구성되는 workbench shell
|
- top titlebar, 우측 activity rail, 중앙 content 전환, 우측 Agent dock, 하단 IOP section slot으로 구성되는 workbench shell
|
||||||
- IOP 관리 page/tab에 외부 IOP console package 또는 widget을 선택적으로 mount하는 제품 조립 경계
|
- IOP 관리 page/tab에 외부 IOP console package 또는 widget을 선택적으로 mount하는 제품 조립 경계
|
||||||
- IOP 호출에 넘길 task metadata 구성
|
- IOP 호출에 넘길 task metadata 구성
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ NomadCode의 Flutter-first 제품 UI 도메인이다. 모바일/데스크톱/선
|
||||||
|
|
||||||
- `lib/main.dart` — Flutter app entrypoint.
|
- `lib/main.dart` — Flutter app entrypoint.
|
||||||
- `lib/src/app/nomadcode_client_app.dart` — MaterialApp host와 integration lifecycle wiring.
|
- `lib/src/app/nomadcode_client_app.dart` — MaterialApp host와 integration lifecycle wiring.
|
||||||
- `lib/src/app/nomadcode_workbench_shell.dart` — top titlebar, 우측 activity rail, 중앙 content 전환, 우측 Agent dock panel을 가진 workbench shell.
|
- `lib/src/app/nomadcode_workbench_shell.dart` — top titlebar, 우측 activity rail, 중앙 content 전환, 우측 Agent dock panel, 하단 IOP slot을 가진 workbench shell.
|
||||||
- `lib/src/features/workspaces/presentation/workspace_home_page.dart` — workspace project/task UI. workbench mount 시 `showAppBar: false`로 내부 app bar를 숨길 수 있다.
|
- `lib/src/features/workspaces/presentation/workspace_home_page.dart` — workspace project/task UI. workbench mount 시 `showAppBar: false`로 내부 app bar를 숨길 수 있다.
|
||||||
- `lib/src/integrations/mattermost/mattermost_auth_service.dart` — Mattermost auth flow.
|
- `lib/src/integrations/mattermost/mattermost_auth_service.dart` — Mattermost auth flow.
|
||||||
- `lib/src/integrations/mattermost/mattermost_push_host_integration.dart` — Mattermost push host initialization, auth handoff, notification navigation.
|
- `lib/src/integrations/mattermost/mattermost_push_host_integration.dart` — Mattermost push host initialization, auth handoff, notification navigation.
|
||||||
|
|
@ -48,7 +48,8 @@ NomadCode의 Flutter-first 제품 UI 도메인이다. 모바일/데스크톱/선
|
||||||
- Android native push 변경은 Kotlin/Java receiver/service, manifest, Dart bridge 영향을 함께 확인한다.
|
- Android native push 변경은 Kotlin/Java receiver/service, manifest, Dart bridge 영향을 함께 확인한다.
|
||||||
- public API 호출을 추가하면 contracts/core 영향 여부를 확인한다.
|
- public API 호출을 추가하면 contracts/core 영향 여부를 확인한다.
|
||||||
- project/task/agent/workspace 제품 UI는 Flutter 앱을 기준 구현으로 둔다.
|
- project/task/agent/workspace 제품 UI는 Flutter 앱을 기준 구현으로 둔다.
|
||||||
- NomadCode app shell은 top titlebar와 우측 activity rail을 가진 workbench 구조를 기준으로 한다. 우측 Agent dock은 rail에 붙는 panel로 열고 닫으며, 중앙 영역은 workspace/workflow/IOP/web context/settings 같은 product section을 전환한다.
|
- NomadCode app shell은 top titlebar와 우측 activity rail을 가진 workbench 구조를 기준으로 한다. 우측 Agent dock은 rail에 붙는 panel로 열고 닫으며, 중앙 영역은 workspace/workflow/terminal/review/web context/settings 같은 product section을 전환한다.
|
||||||
|
- IOP entry는 우측 rail 하단에 둔다. IOP-specific 운영 화면은 `iopContent` 같은 선택적 widget slot으로 mount하고, IOP package가 없는 기본 빌드에서는 placeholder로 유지한다.
|
||||||
- 공통 agent interaction UI는 sibling `agent_shell` package를 사용한다. NomadCode-specific workflow/web context capability는 NomadCode mobile domain이 소유하고, IOP-specific 운영 화면은 향후 IOP-owned package/widget을 선택적으로 mount한다.
|
- 공통 agent interaction UI는 sibling `agent_shell` package를 사용한다. NomadCode-specific workflow/web context capability는 NomadCode mobile domain이 소유하고, IOP-specific 운영 화면은 향후 IOP-owned package/widget을 선택적으로 mount한다.
|
||||||
- `flutter test`를 기본 검증으로 사용한다.
|
- `flutter test`를 기본 검증으로 사용한다.
|
||||||
- 현재 baseline의 `avoid_print` info는 별도 cleanup 작업으로 다루고, 기능 변경과 섞지 않는다.
|
- 현재 baseline의 `avoid_print` info는 별도 cleanup 작업으로 다루고, 기능 변경과 섞지 않는다.
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,9 @@ The Flutter host application in this monorepo is standardized under `apps/client
|
||||||
|
|
||||||
## Workbench Shell
|
## Workbench Shell
|
||||||
|
|
||||||
`lib/src/app/nomadcode_workbench_shell.dart` owns the current NomadCode client shell. It provides a top titlebar, a right-side activity rail, center content switching, and an Agent dock panel attached to the right rail.
|
`lib/src/app/nomadcode_workbench_shell.dart` owns the current NomadCode client shell. It provides a top titlebar, a right-side activity rail, center content switching, and an Agent dock panel attached to the right rail. The rail keeps product/workflow placeholder icons near the top and the IOP entry at the bottom.
|
||||||
|
|
||||||
The Agent dock uses the sibling `agent_shell` package as a product-neutral chat/agent surface. Workflow, Web Context, and optional IOP management areas are mounted as center content sections. IOP-specific UI should be composed through an IOP-owned package or widget boundary rather than copied into NomadCode client code.
|
The Agent dock uses the sibling `agent_shell` package as a product-neutral chat/agent surface. Workflow, Web Context, and optional IOP management areas are mounted as center content sections. IOP-specific UI should be composed through an IOP-owned package or widget boundary via the optional `iopContent` slot rather than copied into NomadCode client code.
|
||||||
|
|
||||||
`WorkspaceHomePage` can hide its own app bar through `showAppBar: false` when it is mounted inside the workbench shell.
|
`WorkspaceHomePage` can hide its own app bar through `showAppBar: false` when it is mounted inside the workbench shell.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,25 @@
|
||||||
import 'package:agent_shell/agent_shell.dart';
|
import 'package:agent_shell/agent_shell.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
enum NomadWorkbenchSection { workspace, workflow, iop, webContext, settings }
|
enum NomadWorkbenchSection {
|
||||||
|
workspace,
|
||||||
|
workflow,
|
||||||
|
terminal,
|
||||||
|
review,
|
||||||
|
webContext,
|
||||||
|
settings,
|
||||||
|
iop,
|
||||||
|
}
|
||||||
|
|
||||||
class NomadCodeWorkbenchShell extends StatefulWidget {
|
class NomadCodeWorkbenchShell extends StatefulWidget {
|
||||||
final Widget workspace;
|
final Widget workspace;
|
||||||
|
final Widget? iopContent;
|
||||||
|
|
||||||
const NomadCodeWorkbenchShell({super.key, required this.workspace});
|
const NomadCodeWorkbenchShell({
|
||||||
|
super.key,
|
||||||
|
required this.workspace,
|
||||||
|
this.iopContent,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<NomadCodeWorkbenchShell> createState() =>
|
State<NomadCodeWorkbenchShell> createState() =>
|
||||||
|
|
@ -70,10 +83,17 @@ class _NomadCodeWorkbenchShellState extends State<NomadCodeWorkbenchShell> {
|
||||||
title: 'Workflow',
|
title: 'Workflow',
|
||||||
subtitle: 'Workflow management surface will mount here.',
|
subtitle: 'Workflow management surface will mount here.',
|
||||||
),
|
),
|
||||||
NomadWorkbenchSection.iop => const _WorkbenchPlaceholder(
|
NomadWorkbenchSection.terminal => const _WorkbenchPlaceholder(
|
||||||
icon: Icons.hub_outlined,
|
icon: Icons.terminal,
|
||||||
title: 'IOP',
|
title: 'Terminal',
|
||||||
subtitle: 'Optional IOP console package will mount here when included.',
|
subtitle:
|
||||||
|
'Remote command sessions and terminal bridges will mount here.',
|
||||||
|
),
|
||||||
|
NomadWorkbenchSection.review => const _WorkbenchPlaceholder(
|
||||||
|
icon: Icons.rate_review_outlined,
|
||||||
|
title: 'Review',
|
||||||
|
subtitle:
|
||||||
|
'Diff, branch, commit, and PR review surfaces will mount here.',
|
||||||
),
|
),
|
||||||
NomadWorkbenchSection.webContext => const _WorkbenchPlaceholder(
|
NomadWorkbenchSection.webContext => const _WorkbenchPlaceholder(
|
||||||
icon: Icons.web_asset_outlined,
|
icon: Icons.web_asset_outlined,
|
||||||
|
|
@ -85,6 +105,14 @@ class _NomadCodeWorkbenchShellState extends State<NomadCodeWorkbenchShell> {
|
||||||
title: 'Settings',
|
title: 'Settings',
|
||||||
subtitle: 'Workspace and integration settings will mount here.',
|
subtitle: 'Workspace and integration settings will mount here.',
|
||||||
),
|
),
|
||||||
|
NomadWorkbenchSection.iop =>
|
||||||
|
widget.iopContent ??
|
||||||
|
const _WorkbenchPlaceholder(
|
||||||
|
icon: Icons.hub_outlined,
|
||||||
|
title: 'IOP',
|
||||||
|
subtitle:
|
||||||
|
'Optional IOP console package will mount here when included.',
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -195,10 +223,16 @@ class _WorkbenchActivityRail extends StatelessWidget {
|
||||||
onPressed: () => onSelect(NomadWorkbenchSection.workflow),
|
onPressed: () => onSelect(NomadWorkbenchSection.workflow),
|
||||||
),
|
),
|
||||||
_RailButton(
|
_RailButton(
|
||||||
tooltip: 'IOP',
|
tooltip: 'Terminal',
|
||||||
icon: Icons.hub_outlined,
|
icon: Icons.terminal,
|
||||||
selected: selected == NomadWorkbenchSection.iop,
|
selected: selected == NomadWorkbenchSection.terminal,
|
||||||
onPressed: () => onSelect(NomadWorkbenchSection.iop),
|
onPressed: () => onSelect(NomadWorkbenchSection.terminal),
|
||||||
|
),
|
||||||
|
_RailButton(
|
||||||
|
tooltip: 'Review',
|
||||||
|
icon: Icons.rate_review_outlined,
|
||||||
|
selected: selected == NomadWorkbenchSection.review,
|
||||||
|
onPressed: () => onSelect(NomadWorkbenchSection.review),
|
||||||
),
|
),
|
||||||
_RailButton(
|
_RailButton(
|
||||||
tooltip: 'Web Context',
|
tooltip: 'Web Context',
|
||||||
|
|
@ -213,6 +247,12 @@ class _WorkbenchActivityRail extends StatelessWidget {
|
||||||
selected: selected == NomadWorkbenchSection.settings,
|
selected: selected == NomadWorkbenchSection.settings,
|
||||||
onPressed: () => onSelect(NomadWorkbenchSection.settings),
|
onPressed: () => onSelect(NomadWorkbenchSection.settings),
|
||||||
),
|
),
|
||||||
|
_RailButton(
|
||||||
|
tooltip: 'IOP',
|
||||||
|
icon: Icons.hub_outlined,
|
||||||
|
selected: selected == NomadWorkbenchSection.iop,
|
||||||
|
onPressed: () => onSelect(NomadWorkbenchSection.iop),
|
||||||
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -336,8 +376,10 @@ String _sectionLabel(NomadWorkbenchSection section) {
|
||||||
return switch (section) {
|
return switch (section) {
|
||||||
NomadWorkbenchSection.workspace => 'Workspace',
|
NomadWorkbenchSection.workspace => 'Workspace',
|
||||||
NomadWorkbenchSection.workflow => 'Workflow',
|
NomadWorkbenchSection.workflow => 'Workflow',
|
||||||
NomadWorkbenchSection.iop => 'IOP Management',
|
NomadWorkbenchSection.terminal => 'Terminal',
|
||||||
|
NomadWorkbenchSection.review => 'Review',
|
||||||
NomadWorkbenchSection.webContext => 'Web Context',
|
NomadWorkbenchSection.webContext => 'Web Context',
|
||||||
NomadWorkbenchSection.settings => 'Settings',
|
NomadWorkbenchSection.settings => 'Settings',
|
||||||
|
NomadWorkbenchSection.iop => 'IOP Management',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,24 @@ void main() {
|
||||||
expect(find.text('IOP Bridge'), findsOneWidget);
|
expect(find.text('IOP Bridge'), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('Workbench opens agent dock and IOP section from right rail', (
|
||||||
|
WidgetTester tester,
|
||||||
|
) async {
|
||||||
|
await tester.pumpWidget(const NomadCodeClientApp());
|
||||||
|
|
||||||
|
await tester.tap(find.byIcon(Icons.smart_toy_outlined));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.text('Ask the workspace agent'), findsOneWidget);
|
||||||
|
expect(find.textContaining('NomadCode agent is docked'), findsOneWidget);
|
||||||
|
|
||||||
|
await tester.tap(find.byIcon(Icons.hub_outlined));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.text('IOP'), findsOneWidget);
|
||||||
|
expect(find.textContaining('Optional IOP console package'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('WorkspaceHomePage triggers launcher callback on button tap', (
|
testWidgets('WorkspaceHomePage triggers launcher callback on button tap', (
|
||||||
WidgetTester tester,
|
WidgetTester tester,
|
||||||
) async {
|
) async {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue