Compare commits

..

13 Commits

Author SHA1 Message Date
Developer 346b32236a debug: synthesize wheel events for mobile terminal touch scrolling
Replace manual scrollLines/SGR injection with synthetic WheelEvent
instances dispatched into xterm.js's .xterm-viewport. Keep the debug
overlay so we can verify whether the gesture is recognized and whether
the wheel event reaches xterm.js.

Quality gates: typecheck, lint clean.
2026-06-13 21:04:50 +00:00
Developer 8f7f682a92 fix: merge mobile terminal scroll detection improvements 2026-06-13 20:42:52 +00:00
Developer 2ece7074f6 fix: improve mobile terminal alternate-screen detection and scrolling
- Revert touch-action: none on .xterm-viewport so xterm.js can fall back
  to its own viewport scrolling when the custom handler doesn't take over.
- Detect alternate screen via reference equality
  (term.buffer.active === term.buffer.alternate) instead of the
  string, which could report normal buffer incorrectly.
- Lower vertical-scroll activation threshold from 4px to 2px and only
  prevent default once a vertical gesture is recognized.
- In normal buffer use term.scrollLines() so xterm.js handles the buffer
  scroll consistently; in alternate screen continue sending SGR 1006
  mouse-wheel sequences to tmux/vim.

Quality gates: typecheck, lint clean, npm test -- --run 87 passed.
2026-06-13 20:42:52 +00:00
Developer c7ab2de25e fix: merge mobile terminal scrolling improvements 2026-06-13 20:30:52 +00:00
Developer c9b0259993 fix: route mobile terminal swipes to the terminal, not the page layout
- Add touch-action: none and overscroll-behavior: none to mobile terminal
  page, content, wrapper, container, and xterm viewport so the browser
  never treats swipes as page/address-bar scrolling or pull-to-refresh.
- Make .terminal-page.mobile fixed-position to prevent viewport layout
  scroll; keep the overlay toolbar as absolute with pointer-events only
  on interactive parts.
- Rework the mobile touch handler in terminal.tsx:
  * Detect normal vs alternate buffer via term.buffer.active.type instead
    of measuring the DOM viewport, which was unreliable in tmux/vim.
  * Accumulate swipe distance and emit SGR 1006 mouse-wheel sequences in
    steps, so tmux pane scrolling tracks the gesture correctly.
  * Prevent default as soon as the swipe is recognized so the page does
    not start a competing scroll gesture.

Quality gates: npm run typecheck, npm run lint clean, npm test -- --run 87 passed.
2026-06-13 20:30:52 +00:00
Developer 1f2c3dbe2a fix: merge workspace project_name population 2026-06-13 19:59:13 +00:00
Developer d247e44985 fix: include project_name in /workspaces list response
- The top-level GET /workspaces endpoint was returning project_name as
  an empty string because the GitRepository.project relationship was not
  eager-loaded.
- Select the full GitRepository entity and apply selectinload(project)
  so project_name is populated for every workspace row.

Backend quality gates: python3 -m pytest 313 passed, 34 skipped.
2026-06-13 19:59:13 +00:00
Developer d60dec9d62 fix: merge workspace project name visibility 2026-06-13 19:53:43 +00:00
Developer 4913cc7297 fix: make workspace project name visible on mobile and desktop cards
- Move the project name below the workspace title row so it reads as a
  distinct line with a project icon.
- Move the status badge into the title row next to the workspace name,
  preventing it from crowding the project label.
- Add .workspace-title-row flex styles and update .workspace-project-name
  to display inline-flex with a brand-colored project icon.

Quality gates: npm run typecheck, npm run lint clean,
npm test -- --run 87 passed.
2026-06-13 19:53:43 +00:00
Developer 86360661e9 fix: merge nav sessions middle 2026-06-13 19:44:47 +00:00
Developer 1d09193652 fix: put Sessions back in the middle of nav on desktop and mobile
- Reorder desktop sidebar so Sessions sits between spaces/tools groups:
  Home, Projects, Workspaces, Sessions, Tool Workshop, Config Profiles, Settings.
- Reorder mobile bottom nav so Sessions is the center item:
  Home, Spaces, Sessions, Tools, Settings.
- Workspace cards already display the owning project name; no extra change needed.

Quality gates: npm run typecheck, npm run lint clean,
npm test -- --run 87 passed.
2026-06-13 19:44:47 +00:00
Developer 4ced6141ae feat: merge mobile Spaces nav and list views 2026-06-13 19:38:03 +00:00
Developer 530225d37c feat: mobile Spaces nav and list-based project/workspace views
- Combine Projects and Workspaces into a single 'Spaces' grouped mobile
  nav item that opens a bottom-sheet menu.
- Add SpacesBottomSheet component with Projects/Workspaces options.
- Extend MobileListView with optional renderItem prop for rich rows.
- Redesign mobile ProjectsPage rows to show project description and
  repository chips.
- Replace mobile WorkspacesPage list with compact WorkspaceCard grid,
  matching desktop card content.
- Add mobile-list-* CSS and mobile-workspaces-list spacing.

Quality gates: npm run typecheck, npm run lint clean,
npm test -- --run 87 passed.
2026-06-13 19:32:16 +00:00
42 changed files with 645 additions and 309 deletions
+1 -1
View File
@@ -16,7 +16,7 @@ dir: .
Trust boundary: index routes, map orients, source decides.
## role
Infrastructure and deployment configuration for a self-hosted project management platform with containerized services, SSO integration, and AI agent development workflows.
Infrastructure and deployment configuration package for a self-hosted project management platform with OAuth2 authentication, providing containerized orchestration, environment templates, and development tooling.
## parent
-
## children
+2 -2
View File
@@ -18,7 +18,7 @@ index: ./.pi-map.index.md
Trust boundary: index routes, map orients, source decides.
## role
Infrastructure and deployment configuration for a self-hosted project management platform with containerized services, SSO integration, and AI agent development workflows.
Infrastructure and deployment configuration package for a self-hosted project management platform with OAuth2 authentication, providing containerized orchestration, environment templates, and development tooling.
## files
- .env.example | Provides a template of environment variables for configuring a Headquarter application with PostgreSQL, Redis, Authentik SSO, and Docker/Traefik deployment
- .gitignore | Specifies files and directories for Git to ignore across a multi-language project with Python, Node, and custom tooling | dep: Git
@@ -31,7 +31,7 @@ Infrastructure and deployment configuration for a self-hosted project management
- progress.md | Tracks completed and remaining tasks for a backend-frontend code refactoring project organized in 7 phases
- swap-pane | Empty file with no functionality
## arch
Multi-service Docker Compose architecture with PostgreSQL/Redis data layer, Node/Python frontend/backend split, Traefik reverse proxy for TLS termination, and OpenSpec-driven AI agent orchestration with environment-based configuration management.
Docker Compose-based microservices architecture with frontend/backend separation, PostgreSQL/Redis data layer, Traefik reverse proxy integration, and environment-driven configuration management following twelve-factor app principles.
## tags
docker, redis, git, application, postgresql, compose, traefik, project
## symbols
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps
## role
Contains the main deployable application entry points and top-level configurations for the project.
Contains the main deployable application modules or entry points for the project.
## parent
index: ./.pi-map.index.md
map: ./.pi-map.md
+2 -2
View File
@@ -4,10 +4,10 @@ dir: apps
index: apps/.pi-map.index.md
## role
Contains the main deployable application entry points and top-level configurations for the project.
Contains the main deployable application modules or entry points for the project.
## files
## arch
Monorepo-style directory structure typically housing multiple independent applications that share common libraries or modules, each with its own configuration and dependencies.
Modular monolith or microservices architecture with separate application boundaries, each potentially having its own configuration, dependencies, and lifecycle.
## tags
-
## symbols
+1 -1
View File
@@ -14,7 +14,7 @@ Self-hosted FastAPI backend API that manages projects, git repositories, and dev
- uv.lock | Lock file for the uv Python package manager that pins exact dependency versions and their artifact hashes for reproducible installations | dep: uv, Python 3.11+, aiosqlite, alembic, annotated-doc, annotated-types, anyio, ast-serialize, asyncpg, and many other PyPI packages
- wait-for-db.sh | Wait for a PostgreSQL database to become available before executing a command, with configurable retry logic. | dep: nc (netcat), sh (POSIX shell), sleep
## arch
Async Python/FastAPI backend with PostgreSQL (asyncpg), Alembic migrations, multi-stage Docker deployment with Cloudflare tunneling, uv package management, and containerized orchestration with database dependency waiting.
Async Python/FastAPI with PostgreSQL (Alembic migrations), multi-stage Docker deployment with Cloudflare tunneling, uv package management, and containerized service orchestration.
## tags
docker, alembic, python, database, fastapi, postgresql, asyncpg, uvicorn
## symbols
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/api/src
## role
Core application package for the Headquarter API, providing configuration, database connectivity, logging infrastructure, and FastAPI application initialization.
Core API application package that initializes and configures the Headquarter FastAPI backend with database, authentication, logging, and middleware infrastructure.
## parent
index: apps/api/.pi-map.index.md
map: apps/api/.pi-map.md
+2 -2
View File
@@ -4,7 +4,7 @@ dir: apps/api/src
index: apps/api/src/.pi-map.index.md
## role
Core application package for the Headquarter API, providing configuration, database connectivity, structured logging, and FastAPI application initialization.
Core API application package that initializes and configures the Headquarter FastAPI backend with database, authentication, logging, and middleware infrastructure.
## files
- __init__.py | Marks the directory as a Python package for the Headquarter API.
- config.py | Defines application configuration settings with environment-based overrides using Pydantic, including database URLs, service domains, OAuth/Authentik integration, JWT/session settings, and computed properties for environment-specific behavior. | exp: class:Settings, func:build_database_url(user: str, password: str, host: str, port: int, database: str) → str | dep: pydantic, pydantic_settings
@@ -12,7 +12,7 @@ Core application package for the Headquarter API, providing configuration, datab
- logging_config.py | Configures structured JSON logging with correlation ID injection, custom formatters, and HTTP request/exception middleware for a FastAPI application. | exp: class:CorrelationIdFilter, method:filter(self, record: logging.LogRecord) → bool, call:get_correlation_id, class:JSONFormatter, method:format(self, record: logging.LogRecord) → str, call:self.formatTime, call:record.getMessage, call:getattr, call:self.formatException, call:json.dumps, method:formatTime(self, record: logging.LogRecord, datefmt) → str, call:time.strftime, call:time.gmtime, class:RequestLoggingMiddleware, method:dispatch(self, request: Request, call_next: Callable) → Response, call:time.time, call:logger.info, call:call_next, call:int, call:logger.error, call:type, call:traceback.format_exc, class:ExceptionLoggingMiddleware, method:dispatch(self, request: Request, call_next: Callable) → Response, call:call_next, call:logger.critical, call:traceback.format_exc, func:configure_logging(level) → None, call:JSONFormatter, call:logging.StreamHandler, call:console_handler.setFormatter, call:console_handler.addFilter, call:CorrelationIdFilter, call:root_logger.setLevel, call:logging.getLogger("uvicorn").setLevel, call:logging.getLogger("uvicorn.access").setLevel, call:logging.getLogger("sqlalchemy.engine").setLevel, call:logger.info, call:logging.getLevelName | dep: json, logging, sys, time, traceback, collections.abc, fastapi, starlette.middleware.base, src.services.shared.correlation
- main.py | Initializes and configures a FastAPI application for the "Headquarter API" with database setup, middleware, routing, and background services. | exp: func:_sanitize_validation_errors(errors), call:error.get, call:str, call:ctx.items, call:isinstance, call:type, call:sanitized.append, func:validation_exception_handler(request: Request, exc: RequestValidationError), call:exc.errors, call:logger.warning, call:_sanitize_validation_errors, call:JSONResponse, func:on_startup(), call:logger.info, call:init_database, call:logger.error, call:sys.exit, call:_health_monitor.start, call:seed_builtin_tool_types, func:on_shutdown(), call:logger.info, call:_health_monitor.stop | dep: logging, os, fastapi, fastapi.exceptions, fastapi.middleware.cors, fastapi.responses, fastapi.staticfiles, src.api.config, src.api.project, src.api.system, src.api.tool, src.api.user, src.api.workspace, src.config, src.models, src.database, src.logging_config, src.seeds.builtin_tool_types, src.services.instance, src.services.shared, sys, src.api.*
## arch
Layered architecture with environment-based Pydantic config, async SQLAlchemy with retry patterns, structured JSON logging with correlation ID tracking, and modular FastAPI setup with middleware pipeline.
Layered architecture using Pydantic for environment-based configuration, async SQLAlchemy with Alembic migrations, structured JSON logging with correlation IDs, and FastAPI middleware/routing pattern for a service-oriented backend.
## tags
src, database, logging, call:logger.info, api, middleware, fastapi, filter
## symbols
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/api/src/api
## role
Defines shared API infrastructure and reusable validation utilities for the API layer.
Defines shared API infrastructure including reusable Pydantic validators for consistent input validation across API endpoints.
## parent
index: apps/api/src/.pi-map.index.md
map: apps/api/src/.pi-map.md
+2 -2
View File
@@ -4,12 +4,12 @@ dir: apps/api/src/api
index: apps/api/src/api/.pi-map.index.md
## role
Defines shared API infrastructure and reusable validation utilities for the API layer.
Defines shared API infrastructure including reusable Pydantic validators for consistent input validation across API endpoints.
## files
- __init__.py | Marks the directory as a Python package for API routers.
- shared_validators.py | Provides reusable Pydantic validator functions for API schema validation including mount paths, files, environment variables, and volume mounts. | exp: func:validate_mount_path(v: str | None) → str | None, call:v.startswith, raise:ValueError, func:validate_files(v: dict | None, max_size_bytes) → dict | None, call:v.items, call:path.startswith, call:len, call:content.encode, raise:ValueError, func:validate_env_vars(v: dict | None) → dict | None, call:isinstance, raise:ValueError, func:validate_volumes(v: list | None) → list | None, call:isinstance, call:enumerate, raise:ValueError
## arch
Modular package structure with shared Pydantic validators for cross-cutting schema validation concerns.
Modular utility package with functional validation helpers using Pydantic for declarative schema enforcement.
## tags
validate, raise:value, error, call:isinstance, mount, api, init, path
## symbols
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/api/src/api/workspace
## role
Provides FastAPI REST API endpoints for workspace management, including CRUD operations, file access, git version control, and tool instance management within user-scoped workspaces.
Provides FastAPI REST endpoints for workspace management, file operations, git integration, and tool instance management within user workspaces.
## parent
index: apps/api/src/api/.pi-map.index.md
map: apps/api/src/api/.pi-map.md
+3 -3
View File
@@ -4,15 +4,15 @@ dir: apps/api/src/api/workspace
index: apps/api/src/api/workspace/.pi-map.index.md
## role
Provides FastAPI REST API endpoints for workspace management, including CRUD operations, file access, git version control, and tool instance management within user-scoped workspaces.
Provides FastAPI REST endpoints for workspace management, file operations, git integration, and tool instance management within user workspaces.
## files
- __init__.py | Aggregates and re-exports workspace API router modules for centralized access | dep: src.api.workspace.workspace_files, src.api.workspace.workspace_git, src.api.workspace.workspace_instances, src.api.workspace.workspaces
- workspace_files.py | Provides FastAPI endpoints for listing, reading, and writing files within user workspaces with optional git commit integration. | exp: func:_get_workspace(session: AsyncSession, workspace_id: uuid.UUID, user_id: uuid.UUID) → Workspace, call:session.execute, call:select(Workspace).where, call:result.scalar_one_or_none, raise:HTTPException, func:list_files(workspace_id: uuid.UUID, path, user_id, session) → dict, call:_get_workspace, call:FileService, call:service.list_directory, raise:HTTPException, func:get_file_content(workspace_id: uuid.UUID, path: str, user_id, session) → dict, call:_get_workspace, call:FileService, call:service.read_file, raise:HTTPException, func:write_file(workspace_id: uuid.UUID, data: dict, user_id, session) → dict, call:_get_workspace, call:FileService, call:data.get("path", "").strip, call:data.get("message", "").strip, call:service.write_file, call:GitOperations, call:git.commit, raise:HTTPException | dep: uuid, fastapi, sqlalchemy.ext.asyncio, src.auth.dependencies, src.models, src.services.shared.file_service, sqlalchemy, src.services.git.git_operations
- workspace_git.py | Provides FastAPI REST endpoints for git operations (status, branches, commit, push, pull, fetch, checkout, history) scoped to user workspaces. | exp: func:_get_workspace(session: AsyncSession, workspace_id: uuid.UUID, user_id: uuid.UUID) → Workspace, call:session.execute, call:select(Workspace).where, call:result.scalar_one_or_none, raise:HTTPException, func:git_status(workspace_id: uuid.UUID, user_id, session) → dict, call:_get_workspace, call:GitOperations, call:git.status, raise:HTTPException, func:git_branches(workspace_id: uuid.UUID, user_id, session) → dict, call:_get_workspace, call:GitOperations, call:git.branches, raise:HTTPException, func:git_commit(workspace_id: uuid.UUID, data: dict, user_id, session) → dict, call:_get_workspace, call:data.get("message", "").strip, call:GitOperations, call:git.commit, raise:HTTPException, func:git_push(workspace_id: uuid.UUID, user_id, session) → dict, call:_get_workspace, call:GitOperations, call:git.push, raise:HTTPException, func:git_pull(workspace_id: uuid.UUID, user_id, session) → dict, call:_get_workspace, call:GitOperations, call:git.pull, raise:HTTPException, func:git_fetch(workspace_id: uuid.UUID, user_id, session) → dict, call:_get_workspace, call:GitOperations, call:git.fetch, raise:HTTPException, func:git_checkout(workspace_id: uuid.UUID, data: dict, user_id, session) → dict, call:_get_workspace, call:data.get("branch", "").strip, call:GitOperations, call:git.checkout, call:session.commit, raise:HTTPException, func:git_history(workspace_id: uuid.UUID, path, limit, user_id, session) → dict, call:_get_workspace, call:GitOperations, call:git.history, raise:HTTPException | dep: uuid, fastapi, sqlalchemy.ext.asyncio, src.auth.dependencies, src.models, src.services.git.git_operations, sqlalchemy
- workspace_instances.py | FastAPI router providing endpoints to create and list tool instances associated with a specific workspace. | exp: func:_get_workspace(session: AsyncSession, workspace_id: uuid.UUID, user_id: uuid.UUID) → Workspace, call:session.execute, call:select(Workspace).where, call:result.scalar_one_or_none, raise:HTTPException, func:create_workspace_instance(workspace_id: uuid.UUID, data: CreateWorkspaceInstanceRequest, user_id, session) → dict, call:_get_workspace, call:session.get, call:CreateInstanceRequest, call:str, call:create_tool_instance, call:instance.created_at.isoformat, raise:HTTPException, func:list_workspace_instances(workspace_id: uuid.UUID, user_id, session) → list[dict], call:_get_workspace, call:session.execute, call:select(ToolInstance) .where(ToolInstance.workspace_id == workspace_id) .order_by, call:ToolInstance.created_at.desc, call:result.scalars().all, call:str, call:i.created_at.isoformat | dep: uuid, fastapi, sqlalchemy, sqlalchemy.ext.asyncio, src.auth.dependencies, src.models, src.schemas.tool, src.services.tool.instance_service
- workspaces.py | Provides FastAPI CRUD endpoints for managing Git repository workspaces with nested and top-level URL structures, including listing, creating, updating, deleting, and syncing workspaces with instance count tracking. | exp: func:list_all_workspaces(user_id, session) → list[dict], call:select(func.count(ToolInstance.id)) .where(ToolInstance.workspace_id == Workspace.id) .correlate(Workspace) .scalar_subquery, call:func.count, call:session.execute, call:select( Workspace, GitRepository.name.label("repo_name"), GitRepository.project_id, GitRepository.ssh_key_id.label("repo_ssh_key_id"), instance_count.label("instance_count"), ) .join(GitRepository, Workspace.repo_id == GitRepository.id) .where(Workspace.user_id == user_id) .order_by, call:GitRepository.name.label, call:GitRepository.ssh_key_id.label, call:instance_count.label, call:Workspace.created_at.desc, call:result.all, call:str, call:ws.last_sync_at.isoformat, call:ws.created_at.isoformat, call:ws.updated_at.isoformat, func:delete_workspace_top_level(workspace_id: uuid.UUID, force, user_id, session) → dict, call:session.get, call:WorkspaceManager, call:manager.delete, call:session.commit, call:session.rollback, call:logger.error, raise:HTTPException, func:create_workspace_top_level(data: dict, user_id, session) → dict, call:data.get("repo_id", "").strip, call:uuid.UUID, call:session.get, call:data.get("name", "").strip, call:data.get("branch", "main").strip, call:WorkspaceManager, call:manager.create, call:session.add, call:session.commit, call:session.rollback, call:logger.error, call:session.refresh, call:str, call:workspace.created_at.isoformat, raise:HTTPException, func:list_workspaces(project_id: uuid.UUID, repo_id: uuid.UUID, user_id, session) → list[dict], call:_get_repo, call:select(func.count(ToolInstance.id)) .where(ToolInstance.workspace_id == Workspace.id) .correlate(Workspace) .scalar_subquery, call:func.count, call:session.execute, call:select( Workspace, instance_count.label("instance_count"), ) .where(Workspace.repo_id == repo_id) .order_by, call:instance_count.label, call:Workspace.created_at.desc, call:result.all, call:str, call:ws.last_sync_at.isoformat, call:ws.created_at.isoformat, call:ws.updated_at.isoformat, func:create_workspace(project_id: uuid.UUID, repo_id: uuid.UUID, data: dict, user_id, session) → dict, call:_get_repo, call:data.get("name", "").strip, call:data.get("branch", "main").strip, call:WorkspaceManager, call:manager.create, call:session.add, call:session.commit, call:session.rollback, call:logger.error, call:session.refresh, call:str, call:workspace.created_at.isoformat, raise:HTTPException, func:get_workspace_detail(project_id: uuid.UUID, repo_id: uuid.UUID, workspace_id: uuid.UUID, user_id, session) → dict, call:_get_repo, call:_get_workspace, call:session.execute, call:select(func.count(ToolInstance.id)).where, call:func.count, call:result.scalar, call:str, call:workspace.last_sync_at.isoformat, call:workspace.created_at.isoformat, call:workspace.updated_at.isoformat, func:update_workspace(project_id: uuid.UUID, repo_id: uuid.UUID, workspace_id: uuid.UUID, data: dict, user_id, session) → dict, call:_get_repo, call:_get_workspace, call:data.get("name", "").strip, call:data.get("branch", "").strip, call:session.commit, call:session.rollback, call:logger.error, call:str, raise:HTTPException, func:delete_workspace(project_id: uuid.UUID, repo_id: uuid.UUID, workspace_id: uuid.UUID, force, user_id, session) → dict, call:_get_repo, call:_get_workspace, call:WorkspaceManager, call:manager.delete, call:session.commit, call:session.rollback, call:logger.error, raise:HTTPException, func:sync_workspace(project_id: uuid.UUID, repo_id: uuid.UUID, workspace_id: uuid.UUID, user_id, session) → dict, call:_get_repo, call:_get_workspace, call:WorkspaceManager, call:manager.sync, call:session.commit, call:workspace.last_sync_at.isoformat, raise:HTTPException, func:_get_repo(session: AsyncSession, repo_id: uuid.UUID, project_id: uuid.UUID, user_id: uuid.UUID) → GitRepository, call:session.execute, call:select(GitRepository) .where( GitRepository.id == repo_id, GitRepository.project_id == project_id, ) .options, call:selectinload, call:result.scalar_one_or_none, raise:HTTPException, func:_get_workspace(session: AsyncSession, workspace_id: uuid.UUID, repo_id: uuid.UUID) → Workspace, call:session.execute, call:select(Workspace).where, call:result.scalar_one_or_none, raise:HTTPException | dep: logging, uuid, fastapi, sqlalchemy, sqlalchemy.ext.asyncio, sqlalchemy.orm, src.auth.dependencies, src.models, src.services.shared.workspace_manager
- workspaces.py | FastAPI router providing CRUD endpoints for workspace management with nested and top-level URL structures, including listing, creating, updating, deleting, and syncing workspaces tied to Git repositories. | exp: func:list_all_workspaces(user_id, session) → list[dict], call:select(func.count(ToolInstance.id)) .where(ToolInstance.workspace_id == Workspace.id) .correlate(Workspace) .scalar_subquery, call:func.count, call:session.execute, call:select( Workspace, GitRepository, instance_count.label("instance_count"), ) .join(GitRepository, Workspace.repo_id == GitRepository.id) .options(selectinload(GitRepository.project)) .where(Workspace.user_id == user_id) .order_by, call:instance_count.label, call:selectinload, call:Workspace.created_at.desc, call:result.all, call:str, call:ws.last_sync_at.isoformat, call:ws.created_at.isoformat, call:ws.updated_at.isoformat, func:delete_workspace_top_level(workspace_id: uuid.UUID, force, user_id, session) → dict, call:session.get, call:WorkspaceManager, call:manager.delete, call:session.commit, call:session.rollback, call:logger.error, raise:HTTPException, func:create_workspace_top_level(data: dict, user_id, session) → dict, call:data.get("repo_id", "").strip, call:uuid.UUID, call:session.get, call:data.get("name", "").strip, call:data.get("branch", "main").strip, call:WorkspaceManager, call:manager.create, call:session.add, call:session.commit, call:session.rollback, call:logger.error, call:session.refresh, call:str, call:workspace.created_at.isoformat, raise:HTTPException, func:list_workspaces(project_id: uuid.UUID, repo_id: uuid.UUID, user_id, session) → list[dict], call:_get_repo, call:select(func.count(ToolInstance.id)) .where(ToolInstance.workspace_id == Workspace.id) .correlate(Workspace) .scalar_subquery, call:func.count, call:session.execute, call:select( Workspace, instance_count.label("instance_count"), ) .where(Workspace.repo_id == repo_id) .order_by, call:instance_count.label, call:Workspace.created_at.desc, call:result.all, call:str, call:ws.last_sync_at.isoformat, call:ws.created_at.isoformat, call:ws.updated_at.isoformat, func:create_workspace(project_id: uuid.UUID, repo_id: uuid.UUID, data: dict, user_id, session) → dict, call:_get_repo, call:data.get("name", "").strip, call:data.get("branch", "main").strip, call:WorkspaceManager, call:manager.create, call:session.add, call:session.commit, call:session.rollback, call:logger.error, call:session.refresh, call:str, call:workspace.created_at.isoformat, raise:HTTPException, func:get_workspace_detail(project_id: uuid.UUID, repo_id: uuid.UUID, workspace_id: uuid.UUID, user_id, session) → dict, call:_get_repo, call:_get_workspace, call:session.execute, call:select(func.count(ToolInstance.id)).where, call:func.count, call:result.scalar, call:str, call:workspace.last_sync_at.isoformat, call:workspace.created_at.isoformat, call:workspace.updated_at.isoformat, func:update_workspace(project_id: uuid.UUID, repo_id: uuid.UUID, workspace_id: uuid.UUID, data: dict, user_id, session) → dict, call:_get_repo, call:_get_workspace, call:data.get("name", "").strip, call:data.get("branch", "").strip, call:session.commit, call:session.rollback, call:logger.error, call:str, raise:HTTPException, func:delete_workspace(project_id: uuid.UUID, repo_id: uuid.UUID, workspace_id: uuid.UUID, force, user_id, session) → dict, call:_get_repo, call:_get_workspace, call:WorkspaceManager, call:manager.delete, call:session.commit, call:session.rollback, call:logger.error, raise:HTTPException, func:sync_workspace(project_id: uuid.UUID, repo_id: uuid.UUID, workspace_id: uuid.UUID, user_id, session) → dict, call:_get_repo, call:_get_workspace, call:WorkspaceManager, call:manager.sync, call:session.commit, call:workspace.last_sync_at.isoformat, raise:HTTPException, func:_get_repo(session: AsyncSession, repo_id: uuid.UUID, project_id: uuid.UUID, user_id: uuid.UUID) → GitRepository, call:session.execute, call:select(GitRepository) .where( GitRepository.id == repo_id, GitRepository.project_id == project_id, ) .options, call:selectinload, call:result.scalar_one_or_none, raise:HTTPException, func:_get_workspace(session: AsyncSession, workspace_id: uuid.UUID, repo_id: uuid.UUID) → Workspace, call:session.execute, call:select(Workspace).where, call:result.scalar_one_or_none, raise:HTTPException | dep: logging, uuid, fastapi, sqlalchemy, sqlalchemy.ext.asyncio, sqlalchemy.orm, src.auth.dependencies, src.models, src.services.shared.workspace_manager
## arch
Modular router-based architecture using FastAPI with domain-driven decomposition into separate modules (files, git, instances, workspaces), nested URL routing for hierarchical resources, and optional git integration for file operations.
Modular FastAPI router pattern with domain-driven separation (files, git, instances, workspaces) using nested URL structures and git-backed workspace synchronization.
## tags
workspace, get, raise:httpexception, call:, at.isoformat, git, call:select, call:data.get
## symbols
+8 -9
View File
@@ -36,12 +36,11 @@ async def list_all_workspaces(
result = await session.execute(
select(
Workspace,
GitRepository.name.label("repo_name"),
GitRepository.project_id,
GitRepository.ssh_key_id.label("repo_ssh_key_id"),
GitRepository,
instance_count.label("instance_count"),
)
.join(GitRepository, Workspace.repo_id == GitRepository.id)
.options(selectinload(GitRepository.project))
.where(Workspace.user_id == user_id)
.order_by(Workspace.created_at.desc())
)
@@ -51,11 +50,11 @@ async def list_all_workspaces(
{
"id": str(ws.id),
"name": ws.name,
"repo_id": str(ws.repo_id),
"repo_name": repo_name or "",
"repo_ssh_key_id": str(ssh_key_id) if ssh_key_id else None,
"project_id": str(project_id) if project_id else "",
"project_name": "",
"repo_id": str(repo.id),
"repo_name": repo.name or "",
"repo_ssh_key_id": str(repo.ssh_key_id) if repo.ssh_key_id else None,
"project_id": str(repo.project_id) if repo.project_id else "",
"project_name": repo.project.name if repo.project else "",
"user_id": str(ws.user_id),
"branch": ws.branch,
"path": ws.path,
@@ -65,7 +64,7 @@ async def list_all_workspaces(
"updated_at": ws.updated_at.isoformat() if ws.updated_at else None,
"instance_count": count or 0,
}
for ws, repo_name, project_id, ssh_key_id, count in rows
for ws, repo, count in rows
]
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/web
## role
Frontend web application serving as the browser-based UI for the project, built with React/Vite/TypeScript and deployed via nginx.
Frontend web application providing a React-based UI with code editing, terminal, and routing capabilities for the "headquarter" project.
## parent
index: apps/.pi-map.index.md
map: apps/.pi-map.md
+2 -2
View File
@@ -4,7 +4,7 @@ dir: apps/web
index: apps/web/.pi-map.index.md
## role
Frontend web application serving as the browser-based UI for the project, built with React/Vite/TypeScript and deployed via nginx.
Frontend web application providing a React-based UI with code editing, terminal, and routing capabilities for the "headquarter" project.
## files
- .env.example | Template file defining example environment variables for frontend API and application URL configuration
- .eslintrc.cjs | Configures ESLint for a TypeScript browser project with modern ECMAScript module support | dep: @typescript-eslint/parser, @typescript-eslint/eslint-plugin, eslint
@@ -16,7 +16,7 @@ Frontend web application serving as the browser-based UI for the project, built
- tsconfig.json | TypeScript configuration file for a React project using Vite with modern ES2020 target and bundler module resolution | dep: typescript, react, vite
- vite.config.ts | Configures Vite build tool for a React project with custom dev server port and Vitest test settings. | dep: vite, @vitejs/plugin-react
## arch
Modern SPA architecture using Vite for bundling, React for UI, TypeScript for type safety, with containerized nginx deployment and environment-driven configuration.
Modern React SPA built with Vite and TypeScript, using nginx for production serving with client-side routing, multi-stage Docker deployment, and Vitest for testing.
## tags
react, eslint, vite, typescript, dom, application, nginx, web
## symbols
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/web/src
## role
Provides the core web application entry point, routing infrastructure, and shared domain type definitions for a React-based frontend.
Frontend web application entry point and core infrastructure for a React-based collaborative development platform.
## parent
index: apps/web/.pi-map.index.md
map: apps/web/.pi-map.md
+2 -2
View File
@@ -4,13 +4,13 @@ dir: apps/web/src
index: apps/web/src/.pi-map.index.md
## role
Provides the core web application entry point, routing infrastructure, and shared domain type definitions for a React-based frontend.
Frontend web application entry point and core infrastructure for a React-based collaborative development platform.
## files
- main.tsx | Bootstraps a React application with routing, authentication, and session management providers. | dep: react, react-dom/client, react-router-dom, ./router, ./state/auth, ./state/sessions, ./styles/tokens.css, ./styles/global.css, ./styles/utilities.css, ./styles/syntax-highlight.css, ./styles/pages/git-history.css, ./styles/pages/projects.css, ./styles/pages/sessions.css, ./styles/pages/ssh-keys.css, ./styles/pages/workspace-detail.css, ./styles/pages/workspaces.css, react-dom
- router.tsx | Defines the React Router configuration for a web application with protected routes, nested layouts, and redirects. | exp: AppRouter | dep: react-router-dom, ./components/app-shell, ./components/protected-route, ./pages/DashboardPage, ./pages/PlaceholderPage, ./pages/ProfilePage, ./pages/ProjectsPage, ./pages/GitRepositoriesPage, ./pages/GitHistoryPage, ./pages/ProjectSettingsPage, ./pages/SettingsPage, ./pages/TerminalPage, ./pages/ToolWorkshopPage, ./pages/SshKeysPage, ./pages/ConfigProfilesPage, ./pages/SessionsPage, ./pages/WorkspacesPage, ./pages/WorkspaceDetailPage
- types.ts | Defines TypeScript type definitions for user sessions, projects, repositories, and workspaces in an application. | exp: SessionUser, SessionPayload, Project, WorkspaceSummary, RepositorySummary, ProjectWithRepos
## arch
Layered architecture with provider composition pattern for cross-cutting concerns (auth, routing, sessions), declarative route configuration with nested layouts and route guards, and centralized TypeScript domain modeling.
Modular React SPA using React Router v6 with nested route layouts, protected route guards via authentication context, and centralized TypeScript domain models for session/workspace/project entities.
## tags
pages, styles, css, router, react, session, dom, project
## symbols
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/web/src/components
## role
Provides reusable, accessible React UI components and utilities for the web application including layout shells, data display states, icon system, code editing, routing guards, and toast notification infrastructure.
Provides reusable, accessible UI components and utilities for rendering the application shell, data states, icons, code display, notifications, and route protection in a React web application.
## parent
index: apps/web/src/.pi-map.index.md
map: apps/web/src/.pi-map.md
+3 -3
View File
@@ -4,9 +4,9 @@ dir: apps/web/src/components
index: apps/web/src/components/.pi-map.index.md
## role
Provides reusable, accessible React UI components and utilities for the web application including layout shells, data display states, icon system, code editing, routing guards, and toast notification infrastructure.
Provides reusable, accessible UI components and utilities for rendering the application shell, data states, icons, code display, notifications, and route protection in a React web application.
## files
- app-shell.tsx | Renders the main application shell layout with navigation, session management, and responsive mobile/desktop views for a React Router-based app. | exp: AppShell | dep: react-router-dom, ../api/sessions, ../hooks/use-theme, ../state/auth, ../state/sessions, ../hooks/use-mobile-viewport, ../state/events, ../state/toast, ../state/notifications, ./features/notification/event-toast-bridge, ./features/notification/notification-center, ./icon, ./features/mobile/mobile-nav, ./features/tool/start-tool-fab, ../utils/icons
- app-shell.tsx | Renders the main application shell layout with navigation, header, session management, and mobile-responsive behavior for a React Router-based SPA. | exp: AppShell | dep: react-router-dom, ../api/sessions, ../hooks/use-theme, ../state/auth, ../state/sessions, ../hooks/use-mobile-viewport, ../state/events, ../state/toast, ../state/notifications, ./features/notification/event-toast-bridge, ./features/notification/notification-center, ./icon, ./features/mobile/mobile-nav, ./features/tool/start-tool-fab, ../utils/icons
- code-editor.tsx | A React component that renders a syntax-highlighted code editor with line numbers using react-simple-code-editor. | exp: CodeEditor | dep: react, react-simple-code-editor, ../utils/language
- data-states.tsx | Provides reusable React components for displaying loading, error, and empty data states in a UI. | exp: LoadingState, ErrorState, EmptyState | dep: ./icon, icon
- icon.tsx | Provides a centralized, type-safe React icon component that maps semantic names to Phosphor icons with configurable size, weight, color, and accessibility attributes. | exp: IconName, IconProps, Icon | dep: react, @phosphor-icons/react
@@ -17,7 +17,7 @@ Provides reusable, accessible React UI components and utilities for the web appl
- toast-rules.test.ts | Unit tests for mapping instance events to toast notification categories and severities | dep: vitest, ./toast-rules, ../types/events
- toast-rules.ts | Maps instance events to toast notifications with deduplication logic to prevent spam | exp: func:mapEventToCategory(event: InstanceEventPayload) → string, call:event.event.startsWith, func:mapEventToSeverity(event: InstanceEventPayload) → "info" | "warning" | "error" | "success", func:handleEventToast(event: InstanceEventPayload) → void, call:shouldShowToast, call:toast.info, call:toast.success, call:toast.warning, call:toast.error, func:clearToastDedup() → void, call:lastToastTime.clear | dep: ../state/toast, ../types/events, toast state module, InstanceEventPayload type
## arch
Component-based architecture with functional React patterns, custom hooks for state management, utility modules for cross-cutting concerns (icons, toasts), and test co-location with implementation files.
Component-based React architecture with functional components, composition patterns, separation of concerns (presentation vs logic), and test coverage for critical utilities; integrates third-party libraries (Phosphor icons, react-simple-code-editor) and React Router for SPA navigation.
## tags
toast, icon, react, state, code, loading, event, editor
## symbols
+2 -2
View File
@@ -22,9 +22,9 @@ const NAV_ITEMS: {
badge?: "sessions";
}[] = [
{ to: "/", label: "Home", icon: "dashboard" },
{ to: "/sessions", label: "Sessions", icon: "terminal", badge: "sessions" },
{ to: "/workspaces", label: "Workspaces", icon: "folder" },
{ to: "/projects", label: "Projects", icon: "projects" },
{ to: "/workspaces", label: "Workspaces", icon: "folder" },
{ to: "/sessions", label: "Sessions", icon: "terminal", badge: "sessions" },
{ to: "/tool-workshop", label: "Tool Workshop", icon: "settings" },
{ to: "/config-profiles", label: "Config Profiles", icon: "folder" },
{ to: "/settings", label: "Settings", icon: "settings" },
@@ -1,52 +1,52 @@
# features (index)
dir: features
# apps/web/src/components/features (index)
dir: apps/web/src/components/features
## role
Contains modular, feature-based components that encapsulate specific domain functionality or user-facing capabilities within the project.
Contains reusable React components that implement specific user-facing features and functionality across the web application.
## parent
index: ./.pi-map.index.md
map: ./.pi-map.md
index: apps/web/src/components/.pi-map.index.md
map: apps/web/src/components/.pi-map.md
## children
- features/config-profiles
index: features/config-profiles/.pi-map.index.md
map: features/config-profiles/.pi-map.md
- features/git
index: features/git/.pi-map.index.md
map: features/git/.pi-map.md
- features/mobile
index: features/mobile/.pi-map.index.md
map: features/mobile/.pi-map.md
- features/notification
index: features/notification/.pi-map.index.md
map: features/notification/.pi-map.md
- features/project
index: features/project/.pi-map.index.md
map: features/project/.pi-map.md
- features/session
index: features/session/.pi-map.index.md
map: features/session/.pi-map.md
- features/settings
index: features/settings/.pi-map.index.md
map: features/settings/.pi-map.md
- features/ssh-keys
index: features/ssh-keys/.pi-map.index.md
map: features/ssh-keys/.pi-map.md
- features/terminal
index: features/terminal/.pi-map.index.md
map: features/terminal/.pi-map.md
- features/tool
index: features/tool/.pi-map.index.md
map: features/tool/.pi-map.md
- features/tool-workshop
index: features/tool-workshop/.pi-map.index.md
map: features/tool-workshop/.pi-map.md
- features/workspace
index: features/workspace/.pi-map.index.md
map: features/workspace/.pi-map.md
- apps/web/src/components/features/config-profiles
index: apps/web/src/components/features/config-profiles/.pi-map.index.md
map: apps/web/src/components/features/config-profiles/.pi-map.md
- apps/web/src/components/features/git
index: apps/web/src/components/features/git/.pi-map.index.md
map: apps/web/src/components/features/git/.pi-map.md
- apps/web/src/components/features/mobile
index: apps/web/src/components/features/mobile/.pi-map.index.md
map: apps/web/src/components/features/mobile/.pi-map.md
- apps/web/src/components/features/notification
index: apps/web/src/components/features/notification/.pi-map.index.md
map: apps/web/src/components/features/notification/.pi-map.md
- apps/web/src/components/features/project
index: apps/web/src/components/features/project/.pi-map.index.md
map: apps/web/src/components/features/project/.pi-map.md
- apps/web/src/components/features/session
index: apps/web/src/components/features/session/.pi-map.index.md
map: apps/web/src/components/features/session/.pi-map.md
- apps/web/src/components/features/settings
index: apps/web/src/components/features/settings/.pi-map.index.md
map: apps/web/src/components/features/settings/.pi-map.md
- apps/web/src/components/features/ssh-keys
index: apps/web/src/components/features/ssh-keys/.pi-map.index.md
map: apps/web/src/components/features/ssh-keys/.pi-map.md
- apps/web/src/components/features/terminal
index: apps/web/src/components/features/terminal/.pi-map.index.md
map: apps/web/src/components/features/terminal/.pi-map.md
- apps/web/src/components/features/tool
index: apps/web/src/components/features/tool/.pi-map.index.md
map: apps/web/src/components/features/tool/.pi-map.md
- apps/web/src/components/features/tool-workshop
index: apps/web/src/components/features/tool-workshop/.pi-map.index.md
map: apps/web/src/components/features/tool-workshop/.pi-map.md
- apps/web/src/components/features/workspace
index: apps/web/src/components/features/workspace/.pi-map.index.md
map: apps/web/src/components/features/workspace/.pi-map.md
## files
## links
index: features/.pi-map.index.md
map: features/.pi-map.md
index: apps/web/src/components/features/.pi-map.index.md
map: apps/web/src/components/features/.pi-map.md
## workflows
-
## dirty
+5 -5
View File
@@ -1,13 +1,13 @@
# features
dir: features
# apps/web/src/components/features
dir: apps/web/src/components/features
index: features/.pi-map.index.md
index: apps/web/src/components/features/.pi-map.index.md
## role
Contains modular, feature-based components that encapsulate specific domain functionality or user-facing capabilities within the project.
Contains reusable React components that implement specific user-facing features and functionality across the web application.
## files
## arch
Feature-based modular architecture with self-contained, cohesive units that likely combine related components, logic, and assets to support vertical slicing of application functionality.
Feature-based component organization with domain-specific UI building blocks following React composition patterns, likely co-located with related hooks, utilities, or sub-components for each feature area.
## tags
-
## symbols
@@ -2,7 +2,7 @@
dir: apps/web/src/components/features/mobile
## role
Provides mobile-optimized UI components for a responsive web application, covering navigation, lists, detail views, forms, modals, and specialized terminal interfaces.
Provides mobile-optimized UI components for core application features including navigation, data views, forms, modals, and specialized terminal interfaces.
## parent
index: apps/web/src/components/features/.pi-map.index.md
map: apps/web/src/components/features/.pi-map.md
@@ -18,6 +18,7 @@ map: apps/web/src/components/features/.pi-map.md
- mobile-page-header.tsx
- mobile-terminal-header.tsx
- mobile-terminal-wrapper.tsx
- spaces-bottom-sheet.tsx
## links
index: apps/web/src/components/features/mobile/.pi-map.index.md
map: apps/web/src/components/features/mobile/.pi-map.md
@@ -4,21 +4,22 @@ dir: apps/web/src/components/features/mobile
index: apps/web/src/components/features/mobile/.pi-map.index.md
## role
Provides mobile-optimized UI components for a responsive web application, covering navigation, lists, detail views, forms, modals, and specialized terminal interfaces.
Provides mobile-optimized UI components for core application features including navigation, data views, forms, modals, and specialized terminal interfaces.
## files
- mobile-action-sheet.tsx | Renders a mobile-optimized action sheet modal with title, configurable action buttons, and cancel option. | exp: MobileActionSheetItem, func:MobileActionSheet({ isOpen, onClose, title, actions, }: MobileActionSheetProps), call:useRef, call:useEffect, call:onClose, call:document.addEventListener, call:document.removeEventListener, call:e.stopPropagation, call:actions.map, call:action.onClick | dep: react, ../../icon, icon
- mobile-detail-view.tsx | A React component that renders a mobile-optimized detail view with a header (back button, title, edit/delete actions) and a field list supporting multiple value types (text, code, JSON, boolean). | exp: MobileDetailView | dep: ../../icon, react
- mobile-edit-view.tsx | A React component that renders a mobile-optimized form for editing data with configurable field types and save/cancel actions. | exp: MobileEditView | dep: react
- mobile-fab.tsx | Renders a floating action button component for mobile with an add icon and configurable click handler and label. | exp: MobileFAB | dep: ../../icon, react, icon
- mobile-list-view.tsx | Renders a searchable mobile-friendly list view with optional icons, subtitles, and click handling | exp: MobileListView | dep: ../../icon, ../../../utils/icons, react, icon, icons
- mobile-nav.tsx | Renders a mobile navigation bar with routing links, a tools group button that opens a bottom sheet, and session count badges | exp: MobileNav | dep: react, react-router-dom, ../../icon, ../tool/tools-bottom-sheet, ../../../utils/icons, icon, tools-bottom-sheet, icons utils
- mobile-list-view.tsx | Renders a mobile-optimized list view with optional search, empty state, and customizable item rendering | exp: MobileListView | dep: react, ../../icon, ../../../utils/icons, icon
- mobile-nav.tsx | Renders a mobile navigation bar with grouped items that open bottom sheets and standard items that use React Router links, including active state indicators and session count badges. | exp: MobileNav | dep: react, react-router-dom, ../../icon, ../tool/tools-bottom-sheet, ./spaces-bottom-sheet, ../../../utils/icons
- mobile-page-header.tsx | Renders a mobile-only page header with optional back navigation and custom actions. | exp: func:MobilePageHeader({ title, showBack = true, actions }: MobilePageHeaderProps), call:useNavigate, call:useMobileViewport, call:navigate | dep: react-router-dom, ../../../hooks/use-mobile-viewport, ../../icon, use-mobile-viewport hook, Icon component
- mobile-terminal-header.tsx | Renders a mobile-responsive header for a terminal interface with navigation, title, connection status, font size controls, and close actions. | exp: MobileTerminalHeader | dep: react, ../../icon, icon
- mobile-terminal-wrapper.tsx | Wraps a terminal component with mobile-specific UI including auto-hiding header, virtual keyboard handling, and special keys interface. | exp: MobileTerminalWrapper | dep: react, ../terminal/terminal, ./mobile-terminal-header, ../terminal/special-keys-strip, ../terminal/special-keys-panel, ../../../hooks/use-mobile-viewport, ../../../hooks/use-virtual-keyboard, ../../../hooks/use-auto-hide, ../../../hooks/use-special-keys
- spaces-bottom-sheet.tsx | Renders a mobile bottom sheet navigation menu for switching between "Projects" and "Workspaces" spaces with active state highlighting. | exp: SpacesBottomSheet | dep: react-router-dom, ../../icon, icon
## arch
Feature-based component organization with compound mobile UI patterns (action sheets, FABs, bottom sheets, auto-hiding headers) that mirror native mobile app paradigms, using configurable props for customization and likely React Context or hooks for keyboard/virtual terminal state management.
Compositional React component library with feature-specific mobile adaptations, using bottom sheets/action sheets for mobile-native UX patterns, and wrapper components that inject mobile-specific behaviors (auto-hiding headers, virtual keyboard handling) into existing features.
## tags
mobile, terminal, view, icon, header, react, sheet, action
mobile, terminal, sheet, react, icon, view, header, renders
## symbols
- MobileActionSheet
- MobilePageHeader
@@ -1,76 +1,88 @@
import type { ReactNode } from "react";
import { Icon } from "../../icon";
import type { IconName } from "../../../utils/icons";
interface MobileListItem {
id: string;
title: string;
subtitle?: string;
icon?: string;
status?: string;
id: string;
title: string;
subtitle?: string;
icon?: string;
status?: string;
}
interface MobileListViewProps {
items: MobileListItem[];
onItemClick: (id: string) => void;
onItemDelete?: (id: string) => void;
onItemDuplicate?: (id: string) => void;
emptyMessage?: string;
searchPlaceholder?: string;
onSearch?: (query: string) => void;
items: MobileListItem[];
onItemClick: (id: string) => void;
onItemDelete?: (id: string) => void;
onItemDuplicate?: (id: string) => void;
emptyMessage?: string;
searchPlaceholder?: string;
onSearch?: (query: string) => void;
renderItem?: (item: MobileListItem) => ReactNode;
}
export const MobileListView: React.FC<MobileListViewProps> = ({
items,
onItemClick,
emptyMessage = "No items found",
searchPlaceholder = "Search...",
onSearch,
items,
onItemClick,
emptyMessage = "No items found",
searchPlaceholder = "Search...",
onSearch,
renderItem,
}) => {
return (
<div className="mobile-list-view">
{onSearch && (
<div className="mobile-list-search">
<input
type="search"
placeholder={searchPlaceholder}
onChange={(e) => onSearch(e.target.value)}
className="mobile-list-search-input"
/>
</div>
)}
return (
<div className="mobile-list-view">
{onSearch && (
<div className="mobile-list-search">
<input
type="search"
placeholder={searchPlaceholder}
onChange={(e) => onSearch(e.target.value)}
className="mobile-list-search-input"
/>
</div>
)}
{items.length === 0 ? (
<div className="mobile-list-empty">
<Icon name="folder" size="lg" />
<p>{emptyMessage}</p>
</div>
) : (
<div className="mobile-list-items">
{items.map((item) => (
<button
key={item.id}
className="mobile-list-item"
onClick={() => onItemClick(item.id)}
type="button"
>
{item.icon && (
<div className="mobile-list-item-icon">
<Icon name={item.icon as IconName} size="md" />
</div>
)}
<div className="mobile-list-item-content">
<div className="mobile-list-item-title">{item.title}</div>
{item.subtitle && (
<div className="mobile-list-item-subtitle">{item.subtitle}</div>
)}
</div>
<div className="mobile-list-item-actions" style={{ transform: "rotate(180deg)" }}>
<Icon name="arrow-left" size="sm" />
</div>
</button>
))}
</div>
)}
</div>
);
{items.length === 0 ? (
<div className="mobile-list-empty">
<Icon name="folder" size="lg" />
<p>{emptyMessage}</p>
</div>
) : (
<div className="mobile-list-items">
{items.map((item) => (
<button
key={item.id}
className="mobile-list-item"
onClick={() => onItemClick(item.id)}
type="button"
>
{item.icon && (
<div className="mobile-list-item-icon">
<Icon name={item.icon as IconName} size="md" />
</div>
)}
{renderItem ? (
renderItem(item)
) : (
<div className="mobile-list-item-content">
<div className="mobile-list-item-title">{item.title}</div>
{item.subtitle && (
<div className="mobile-list-item-subtitle">
{item.subtitle}
</div>
)}
</div>
)}
<div
className="mobile-list-item-actions"
style={{ transform: "rotate(180deg)" }}
>
<Icon name="arrow-left" size="sm" />
</div>
</button>
))}
</div>
)}
</div>
);
};
@@ -2,86 +2,118 @@ import { useState } from "react";
import { NavLink, useLocation } from "react-router-dom";
import { Icon } from "../../icon";
import { ToolsBottomSheet } from "../tool/tools-bottom-sheet";
import { SpacesBottomSheet } from "./spaces-bottom-sheet";
import type { IconName } from "../../../utils/icons";
interface MobileNavProps {
sessionCount?: number;
sessionCount?: number;
}
interface NavItem {
to: string;
label: string;
icon: IconName;
isGroup?: boolean;
to: string;
label: string;
icon: IconName;
isGroup?: boolean;
group?: "tools" | "spaces";
}
const MOBILE_NAV_ITEMS: NavItem[] = [
{ to: "/", label: "Home", icon: "dashboard" },
{ to: "/projects", label: "Projects", icon: "projects" },
{ to: "/sessions", label: "Sessions", icon: "terminal" },
{ to: "/tools", label: "Tools", icon: "settings", isGroup: true },
{ to: "/settings", label: "Settings", icon: "settings" },
{ to: "/", label: "Home", icon: "dashboard" },
{
to: "/spaces",
label: "Spaces",
icon: "projects",
isGroup: true,
group: "spaces",
},
{ to: "/sessions", label: "Sessions", icon: "terminal" },
{
to: "/tools",
label: "Tools",
icon: "settings",
isGroup: true,
group: "tools",
},
{ to: "/settings", label: "Settings", icon: "settings" },
];
export const MobileNav: React.FC<MobileNavProps> = ({ sessionCount }) => {
const location = useLocation();
const [toolsSheetOpen, setToolsSheetOpen] = useState(false);
const location = useLocation();
const [toolsSheetOpen, setToolsSheetOpen] = useState(false);
const [spacesSheetOpen, setSpacesSheetOpen] = useState(false);
const isToolsActive =
location.pathname === "/tool-workshop" ||
location.pathname === "/config-profiles";
const isToolsActive =
location.pathname === "/tool-workshop" ||
location.pathname === "/config-profiles";
const handleNavClick = (item: NavItem) => {
if (item.isGroup) {
setToolsSheetOpen(true);
}
};
const isSpacesActive =
location.pathname.startsWith("/projects") ||
location.pathname.startsWith("/workspaces");
return (
<>
<nav className="mobile-nav" role="navigation" aria-label="Mobile navigation">
{MOBILE_NAV_ITEMS.map((item) => {
if (item.isGroup) {
return (
<button
key={item.to}
className={`mobile-nav-item ${isToolsActive ? "active" : ""}`}
onClick={() => handleNavClick(item)}
type="button"
>
<div className="mobile-nav-icon-wrapper">
<Icon name={item.icon} size="md" />
</div>
<span className="mobile-nav-label">{item.label}</span>
</button>
);
}
const handleNavClick = (item: NavItem) => {
if (!item.isGroup) return;
if (item.group === "spaces") {
setSpacesSheetOpen(true);
} else {
setToolsSheetOpen(true);
}
};
return (
<NavLink
key={item.to}
to={item.to}
className={({ isActive }) =>
`mobile-nav-item ${isActive ? "active" : ""}`
}
end={item.to === "/"}
>
<div className="mobile-nav-icon-wrapper">
<Icon name={item.icon} size="md" />
{item.to === "/sessions" && sessionCount ? (
<span className="mobile-nav-badge">{sessionCount}</span>
) : null}
</div>
<span className="mobile-nav-label">{item.label}</span>
</NavLink>
);
})}
</nav>
return (
<>
<nav
className="mobile-nav"
role="navigation"
aria-label="Mobile navigation"
>
{MOBILE_NAV_ITEMS.map((item) => {
if (item.isGroup) {
const isActive =
item.group === "spaces" ? isSpacesActive : isToolsActive;
return (
<button
key={item.to}
className={`mobile-nav-item ${isActive ? "active" : ""}`}
onClick={() => handleNavClick(item)}
type="button"
>
<div className="mobile-nav-icon-wrapper">
<Icon name={item.icon} size="md" />
</div>
<span className="mobile-nav-label">{item.label}</span>
</button>
);
}
<ToolsBottomSheet
isOpen={toolsSheetOpen}
onClose={() => setToolsSheetOpen(false)}
/>
</>
);
return (
<NavLink
key={item.to}
to={item.to}
className={({ isActive }) =>
`mobile-nav-item ${isActive ? "active" : ""}`
}
end={item.to === "/"}
>
<div className="mobile-nav-icon-wrapper">
<Icon name={item.icon} size="md" />
{item.to === "/sessions" && sessionCount ? (
<span className="mobile-nav-badge">{sessionCount}</span>
) : null}
</div>
<span className="mobile-nav-label">{item.label}</span>
</NavLink>
);
})}
</nav>
<ToolsBottomSheet
isOpen={toolsSheetOpen}
onClose={() => setToolsSheetOpen(false)}
/>
<SpacesBottomSheet
isOpen={spacesSheetOpen}
onClose={() => setSpacesSheetOpen(false)}
/>
</>
);
};
@@ -0,0 +1,66 @@
import { useLocation, useNavigate } from "react-router-dom";
import { Icon } from "../../icon";
interface SpacesBottomSheetProps {
isOpen: boolean;
onClose: () => void;
}
const SPACES_ITEMS = [
{ to: "/projects", label: "Projects" },
{ to: "/workspaces", label: "Workspaces" },
];
export const SpacesBottomSheet: React.FC<SpacesBottomSheetProps> = ({
isOpen,
onClose,
}) => {
const location = useLocation();
const navigate = useNavigate();
if (!isOpen) return null;
const handleSelect = (to: string) => {
onClose();
navigate(to);
};
return (
<div
className="mobile-bottom-sheet-overlay"
onClick={onClose}
role="presentation"
>
<div
className="mobile-bottom-sheet"
onClick={(e) => e.stopPropagation()}
role="dialog"
aria-label="Spaces menu"
>
<div className="mobile-bottom-sheet-header">
<div className="mobile-bottom-sheet-handle" />
<h3 className="mobile-bottom-sheet-title">Spaces</h3>
</div>
<div className="mobile-bottom-sheet-content">
{SPACES_ITEMS.map((item) => (
<button
key={item.to}
className={`mobile-bottom-sheet-item ${
location.pathname === item.to ? "active" : ""
}`}
onClick={() => handleSelect(item.to)}
type="button"
>
<span className="mobile-bottom-sheet-item-label">
{item.label}
</span>
{location.pathname === item.to && (
<Icon name="success" size="sm" />
)}
</button>
))}
</div>
</div>
</div>
);
};
@@ -2,7 +2,7 @@
dir: apps/web/src/components/features/terminal
## role
Provides cross-platform terminal UI components for desktop and mobile with session management, special key input, and xterm.js WebSocket connectivity.
Provides a cross-platform terminal emulator interface with WebSocket backend connectivity, supporting multiple sessions, special key input, and responsive desktop/mobile layouts.
## parent
index: apps/web/src/components/features/.pi-map.index.md
map: apps/web/src/components/features/.pi-map.md
@@ -4,7 +4,7 @@ dir: apps/web/src/components/features/terminal
index: apps/web/src/components/features/terminal/.pi-map.index.md
## role
Provides cross-platform terminal UI components for desktop and mobile with session management, special key input, and xterm.js WebSocket connectivity.
Provides a cross-platform terminal emulator interface with WebSocket backend connectivity, supporting multiple sessions, special key input, and responsive desktop/mobile layouts.
## files
- DesktopTerminalView.tsx | Renders a desktop-optimized terminal view with session tabs, fullscreen mode, font controls, and reset confirmation dialog | exp: DesktopTerminalView | dep: react, ./terminal, ./terminal-session-tabs, ../../../api/terminal, React, TerminalComponent, TerminalSessionTabs, TerminalSession type
- MobileTerminalView.tsx | Renders a mobile-optimized terminal interface with toolbar, session tabs, terminal output, and special keys input controls. | exp: MobileTerminalView | dep: react, ./terminal, ./terminal-session-tabs, ../../icon, ./special-keys-strip, ./special-keys-panel, ../../../hooks/use-special-keys, ../../../api/terminal
@@ -12,9 +12,9 @@ Provides cross-platform terminal UI components for desktop and mobile with sessi
- special-keys-strip.tsx | Renders a strip of buttons for sending special keyboard keys (Esc, Tab, arrows, etc.) with optional modifier support for a terminal interface. | exp: SpecialKeysStrip | dep: react, ../../../hooks/use-special-keys, React, use-special-keys hook
- terminal-session-tabs.test.tsx | Tests a React component that renders interactive terminal session tabs with selection, close confirmation, renaming, creation limits, and status indicators. | dep: @testing-library/react, vitest, ./terminal-session-tabs
- terminal-session-tabs.tsx | Renders a tabbed interface for managing multiple terminal sessions with selection, creation, renaming, and close confirmation features. | exp: TerminalSessionInfo, TerminalSessionTabsProps, TerminalSessionTabs | dep: react, React
- terminal.tsx | React component that renders an interactive xterm.js terminal with WebSocket connectivity, mobile touch support, flow control, heartbeat monitoring, and reconnection logic. | exp: TerminalProps, TerminalRef, TerminalComponent | dep: react, xterm, xterm-addon-fit, xterm-addon-web-links, xterm/css/xterm.css, ../../../hooks/use-special-keys, React, use-special-keys hook
- terminal.tsx | A React component that renders an interactive terminal emulator using xterm.js, connecting to a WebSocket backend with features like mobile touch scrolling, font resizing, flow control, heartbeat monitoring, and automatic reconnection. | exp: TerminalProps, TerminalRef, TerminalComponent | dep: react, xterm, xterm-addon-fit, xterm-addon-web-links, xterm/css/xterm.css, ../../../hooks/use-special-keys
## arch
Component-based architecture with platform-specific view variants (Desktop/Mobile), shared session tabs state management, modular special key input panels, and a core terminal component handling WebSocket lifecycle with heartbeat/reconnection logic.
Component-based architecture with platform-specific view variants (Desktop/Mobile), compound component pattern for terminal subsystems (tabs, special keys, xterm.js integration), and WebSocket-based real-time communication with reconnection and heartbeat monitoring.
## tags
terminal, special, keys, session, react, tabs, view, renders
## symbols
@@ -355,13 +355,41 @@ export const TerminalComponent = React.forwardRef<TerminalRef, TerminalProps>(
// In normal mode xterm.js has a scrollable viewport; in alternate
// screen (tmux/vim) there is no scrollback and the only way to
// scroll is to send mouse-wheel protocol sequences to the
// application. We detect which situation we're in by checking
// whether the viewport has scrollable height.
// application. We detect the active buffer by reference equality
// (term.buffer.active === term.buffer.alternate) because the
// `type` string can be unreliable in some xterm.js versions.
let touchCleanup: (() => void) | undefined;
if (isMobile) {
let startY = 0;
let startX = 0;
let isScrolling = false;
let debugOverlay: HTMLDivElement | null = null;
const showDebug = (msg: string) => {
if (!debugOverlay) {
debugOverlay = document.createElement("div");
debugOverlay.style.position = "fixed";
debugOverlay.style.bottom = "120px";
debugOverlay.style.left = "8px";
debugOverlay.style.right = "8px";
debugOverlay.style.zIndex = "9999";
debugOverlay.style.background = "rgba(0,0,0,0.8)";
debugOverlay.style.color = "#0f0";
debugOverlay.style.fontFamily = "monospace";
debugOverlay.style.fontSize = "11px";
debugOverlay.style.padding = "8px";
debugOverlay.style.borderRadius = "6px";
debugOverlay.style.pointerEvents = "none";
debugOverlay.style.whiteSpace = "pre-wrap";
debugOverlay.style.maxHeight = "120px";
debugOverlay.style.overflow = "auto";
document.body.appendChild(debugOverlay);
}
const line = `${new Date().toLocaleTimeString()} ${msg}`;
debugOverlay.textContent =
line +
"\n" +
debugOverlay.textContent?.split("\n").slice(0, 8).join("\n");
};
const onTouchStart = (e: TouchEvent) => {
if (e.touches.length === 1) {
@@ -375,46 +403,49 @@ export const TerminalComponent = React.forwardRef<TerminalRef, TerminalProps>(
const touch = e.touches[0];
const deltaY = startY - touch.clientY;
const deltaX = Math.abs(startX - touch.clientX);
showDebug(`move dy=${deltaY.toFixed(0)} dx=${deltaX.toFixed(0)} scrolling=${isScrolling}`);
// Decide early whether this is a vertical scroll gesture.
if (!isScrolling) {
if (Math.abs(deltaY) > deltaX && Math.abs(deltaY) > 4) {
if (Math.abs(deltaY) > deltaX && Math.abs(deltaY) > 2) {
isScrolling = true;
}
}
if (isScrolling) {
e.preventDefault();
const viewport = container.querySelector(
".xterm-viewport",
) as HTMLElement | null;
if (!viewport) return;
if (!isScrolling) return;
// If the viewport is scrollable, scroll it directly.
// Otherwise we are in alternate screen (tmux/vim) and must
// send SGR 1006 mouse-wheel protocol data.
const hasScrollback = viewport.scrollHeight > viewport.clientHeight;
if (hasScrollback) {
viewport.scrollTop += deltaY;
} else {
const ws = wsRef.current;
if (ws?.readyState === WebSocket.OPEN && termRef.current) {
// Use the cursor position as the wheel location so
// tmux knows which pane to scroll.
const buf = termRef.current.buffer.active;
const col = buf.cursorX + 1;
const row = buf.cursorY + 1;
// SGR 1006: 64 = wheel-up, 65 = wheel-down
const btn = deltaY > 0 ? 64 : 65;
ws.send(`\x1b[<${btn};${col};${row}M`);
}
}
startY = touch.clientY;
// Take over the gesture so the browser never scrolls the
// page or the terminal canvas. We synthesize wheel events
// and dispatch them into xterm.js, which already handles
// both normal buffer scrolling and alternate-screen apps
// (tmux/vim) correctly.
e.preventDefault();
const target = container.querySelector(".xterm-viewport") as
| HTMLElement
| null;
if (target) {
showDebug(`dispatch wheel deltaY=${deltaY.toFixed(0)}`);
const wheel = new WheelEvent("wheel", {
deltaY,
deltaX: 0,
deltaZ: 0,
clientX: touch.clientX,
clientY: touch.clientY,
screenX: touch.screenX,
screenY: touch.screenY,
bubbles: true,
cancelable: true,
});
target.dispatchEvent(wheel);
}
startY = touch.clientY;
};
const onTouchEnd = () => {
isScrolling = false;
};
container.addEventListener("touchstart", onTouchStart, {
passive: true,
passive: false,
capture: true,
});
container.addEventListener("touchmove", onTouchMove, {
@@ -2,7 +2,7 @@
dir: apps/web/src/components/features/workspace
## role
Provides React UI components for workspace management, navigation, and detail views in a web-based development environment.
Provides the complete UI component suite for workspace management, including creation, detail viewing, file editing, Git operations, tool management, and settings.
## parent
index: apps/web/src/components/features/.pi-map.index.md
map: apps/web/src/components/features/.pi-map.md
@@ -4,9 +4,9 @@ dir: apps/web/src/components/features/workspace
index: apps/web/src/components/features/workspace/.pi-map.index.md
## role
Provides React UI components for workspace management, navigation, and detail views in a web-based development environment.
Provides the complete UI component suite for workspace management, including creation, detail viewing, file editing, Git operations, tool management, and settings.
## files
- workspace-card.tsx | React card component that displays workspace information with status, metadata, and action buttons | exp: WorkspaceCardProps, func:WorkspaceCard({ workspace, loading = false, onStartTool, onSync, onDelete, }: WorkspaceCardProps), call:onStartTool, call:onSync, call:onDelete | dep: react-router-dom, ../../icon, ./workspace-instance-chips, ../../../types/workspace
- workspace-card.tsx | React component that renders a card displaying workspace information with status, metadata, and action buttons. | exp: WorkspaceCardProps, func:WorkspaceCard({ workspace, loading = false, onStartTool, onSync, onDelete, }: WorkspaceCardProps), call:onStartTool, call:onSync, call:onDelete | dep: react-router-dom, ../../icon, ./workspace-instance-chips, ../../../types/workspace
- workspace-create-form.tsx | React form component for creating workspaces with cascading project/repository/branch selectors and support for both contextual and standalone modes | exp: WorkspaceCreateFormProps, func:WorkspaceCreateForm({ onSubmit, onCancel, defaultProjectId, defaultRepoId, }: WorkspaceCreateFormProps), call:Boolean, call:useState, call:useGitRepo, call:useEffect, call:git.branches.includes, call:setSelectedBranch, call:setIsNewBranch, call:useCallback, call:listProjects, call:setProjects, call:setSelectedProject, call:setError, call:setFetchingProjects, call:loadProjects, call:setRepos, call:setSelectedRepo, call:listRepositories, call:loadRepos, call:setNewBranchName, call:e.preventDefault, call:name.trim, call:newBranchName.trim, call:setSubmitting, call:createWorkspaceTopLevel, call:onSubmit, call:projects.map, call:repos.map, call:setName, call:handleBranchChange, call:git.branches.map | dep: react, ../../icon, ../../../api/projects, ../../../api/git-repositories, ../../../api/workspaces, ../../../hooks/use-git-repo, ../../../types, icon, api/projects, api/git-repositories, api/workspaces, hooks/use-git-repo, types
- workspace-detail-header.tsx | Renders a header component for a workspace detail page showing breadcrumb navigation and branch information. | exp: WorkspaceDetailHeaderProps, func:WorkspaceDetailHeader({ workspace, }: WorkspaceDetailHeaderProps) | dep: ../../icon, react
- workspace-file-panel.tsx | Renders a file browser and editor panel with Git integration for a workspace detail page. | exp: func:WorkspaceFilePanel({ workspaceId }: WorkspaceFilePanelProps), call:useWorkspaceFiles, call:useWorkspaceGit, call:useState, call:setSelectedPath, call:setIsEditing, call:setEditContent, call:navigateTo, call:loadFile, call:currentPath.split("/").slice(0, -1).join, call:saveFile, call:setCommitMessage, call:commit, call:entries.map, call:handleSelect | dep: react, ../../icon, ../../../hooks/use-workspace-files, ../../../hooks/use-workspace-git, ../../../api/workspace-files
@@ -17,7 +17,7 @@ Provides React UI components for workspace management, navigation, and detail vi
- workspace-tab-bar.tsx | Renders desktop and mobile tab bar components for workspace navigation with files, git, tools, and settings tabs. | exp: WorkspaceTab, func:WorkspaceTabBar({ active, onChange }: WorkspaceTabBarProps), call:TABS.map, call:onChange, func:WorkspaceMobileTabBar({ active, onChange, }: WorkspaceTabBarProps), call:TABS.map, call:onChange | dep: ../../icon, react
- workspace-tools-panel.tsx | Renders a tools management panel for a workspace that displays running tool instances and allows starting new tools via a modal | exp: func:WorkspaceToolsPanel({ workspace }: WorkspaceToolsPanelProps), call:useWorkspaceInstances, call:useState, call:setShowModal, call:instances.map, call:e.stopPropagation, call:refresh | dep: react, ../../icon, ../tool/tool-starter, ../../../hooks/use-workspace-instances, ../../../types/workspace, icon, tool-starter, use-workspace-instances, workspace types
## arch
Feature-based component organization with page-specific composite components (header/detail/settings) and reusable atomic pieces (cards, chips, forms), following a panel/tab architecture for workspace detail layout.
Feature-based component organization with compound page pattern (header/tab-bar/panels), form abstraction for creation, and modal-driven tool management with responsive mobile/desktop tab navigation.
## tags
workspace, call:set, call:use, panel, git, branch, react, call:on
## symbols
@@ -35,15 +35,18 @@ export function WorkspaceCard({
className="workspace-title-link"
>
<div className="workspace-title-stack">
<div className="workspace-title-row">
<h4 className="workspace-name">{workspace.name}</h4>
<span className={`status-badge ${statusClass}`}>
{workspace.status}
</span>
</div>
<span className="workspace-project-name">
<Icon name="projects" size="sm" />
{workspace.project_name}
</span>
<h4 className="workspace-name">{workspace.name}</h4>
</div>
</Link>
<span className={`status-badge ${statusClass}`}>
{workspace.status}
</span>
</div>
<div className="workspace-card-body">
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/web/src/pages
## role
Contains top-level page components that serve as route endpoints for the web application, each rendering distinct feature areas of the developer workspace platform.
Contains top-level page components that serve as route endpoints for the web application's primary UI surfaces, each handling a specific domain area (dashboard, projects, workspaces, git, settings, etc.) with responsive layouts and CRUD operations.
## parent
index: apps/web/src/.pi-map.index.md
map: apps/web/src/.pi-map.md
+5 -5
View File
@@ -4,10 +4,10 @@ dir: apps/web/src/pages
index: apps/web/src/pages/.pi-map.index.md
## role
Contains top-level page components that serve as route endpoints for the web application, each rendering distinct feature areas of the developer workspace platform.
Contains top-level page components that serve as route endpoints for the web application's primary UI surfaces, each handling a specific domain area (dashboard, projects, workspaces, git, settings, etc.) with responsive layouts and CRUD operations.
## files
- ConfigProfilesPage.tsx | Renders a responsive configuration profiles management page with sidebar list and editor panel for desktop, and a dedicated mobile view for creating, editing, and managing config profiles. | exp: ConfigProfilesPage | dep: react, ../components/data-states, ../hooks/use-mobile-viewport, ../hooks/use-config-profiles, ../components/features/config-profiles/ConfigProfileListSidebar, ../components/features/config-profiles/ConfigProfileEditorPanel, ../components/features/config-profiles/ConfigProfilesMobileView
- DashboardPage.test.tsx | Tests the DashboardPage (HomePage) component's rendering, loading states, and error handling with retry functionality | dep: @testing-library/jest-dom/vitest, @testing-library/react, react-router-dom, vitest, ./DashboardPage, ../state/sessions, ../state/session-operations, ../api/dashboard, ../api/sessions, ../api/projects, ../api/git-repositories, ../api/tool-types
- DashboardPage.test.tsx | Unit tests for the DashboardPage component verifying overview loading and error retry behavior | dep: @testing-library/jest-dom/vitest, @testing-library/react, react-router-dom, vitest, ./DashboardPage, ../state/sessions, DashboardPage, SessionsProvider
- DashboardPage.tsx | Renders a dashboard homepage that displays workspace overview, active/recent sessions, summary statistics, and polling health checks for running instances. | exp: HomePage | dep: react, react-router-dom, ../api/dashboard, ../api/sessions, ../components/data-states, ../components/features/session/session-list, ../hooks/use-instance-actions, ../state/sessions
- GitHistoryPage.tsx | Renders a Git commit history page with branch selection, commit list with graph visualization, and a detail panel showing commit metadata, stats, and diffs. | exp: GitHistoryPage | dep: react, react-router-dom, ../api/git-repositories, ../components/data-states, ../components/icon, ../hooks/use-async-data
- GitRepositoriesPage.tsx | Displays and manages a project's Git repositories with CRUD operations including listing, creating, navigating to history, and deleting with confirmation | exp: GitRepositoriesPage | dep: react, react-router-dom, ../api/git-repositories, ../components/data-states, ../components/icon, ../components/features/project/repository-create-dialog, ../hooks/use-async-data
@@ -15,7 +15,7 @@ Contains top-level page components that serve as route endpoints for the web app
- ProfilePage.tsx | A React component that displays and allows editing of a user profile, including name, email, and avatar upload with validation. | exp: ProfilePage | dep: react, ../api/profile, ../components/data-states, ../components/icon, ../state/auth, ../hooks/use-async-data
- ProjectSettingsPage.tsx | Renders a project settings page with tabbed navigation for general settings, repositories, and members, including project data fetching, editing, and deletion capabilities. | exp: ProjectSettingsPage | dep: react, react-router-dom, ../components/features/settings/settings-tab-layout, ../components/features/project/repositories-settings-tab, ../api/client, ../types
- ProjectsPage.test.tsx | Unit tests for the ProjectsPage component covering loading, empty, error, create, edit, and delete states with API mocking. | dep: @testing-library/react, react-router-dom, vitest, ./ProjectsPage, ../api/projects, ProjectsPage
- ProjectsPage.tsx | Renders a responsive projects management page with separate mobile and desktop layouts, supporting project CRUD operations, repository creation, and workspace management. | exp: ProjectsPage | dep: react, ../components/data-states, ../components/icon, ../hooks/use-mobile-viewport, ../components/features/project/ProjectCard, ../components/features/project/ProjectDialog, ../components/features/project/repository-create-dialog, ../components/features/mobile/mobile-list-view, ../components/features/mobile/mobile-fab, ../hooks/use-projects, ../types
- ProjectsPage.tsx | Renders a responsive projects management page with separate mobile and desktop layouts, supporting project CRUD operations, repository management, and workspace actions. | exp: ProjectsPage | dep: react, ../components/data-states, ../components/icon, ../hooks/use-mobile-viewport, ../components/features/project/ProjectCard, ../components/features/project/ProjectDialog, ../components/features/project/repository-create-dialog, ../components/features/mobile/mobile-list-view, ../components/features/mobile/mobile-fab, ../hooks/use-projects, ../types
- SessionsPage.tsx | Renders a sessions management page that displays, polls health for, and handles CRUD operations on development environment sessions with dirty delete confirmation. | exp: SessionsPage | dep: react, ../api/sessions, ../api/settings, ../components/data-states, ../components/features/session/session-list, ../components/features/session/session-card, ../hooks/use-instance-actions, ../state/sessions
- SettingsPage.tsx | A React settings page component that loads, displays, and manages user configuration with tabbed navigation and nested outlet for child routes. | exp: SettingsPage | dep: react, react-router-dom, ../api/settings, ../components/data-states, ../hooks/use-async-data, ../components/features/settings/GeneralSettingsTab
- SshKeysPage.tsx | React page component for managing SSH keys including generation, listing, signing, verification, and deletion | exp: SSHKeysPage | dep: react-router-dom, ../components/data-states, ../hooks/use-ssh-keys, ../components/features/ssh-keys/SSHKeyCreateForm, ../components/features/ssh-keys/SSHKeyList
@@ -23,9 +23,9 @@ Contains top-level page components that serve as route endpoints for the web app
- ToolWorkshopPage.tsx | Renders a responsive tool workshop page with sidebar/editor layout for desktop and tabbed mobile view for managing tool types | exp: ToolWorkshopPage | dep: ../components/data-states, ../hooks/use-mobile-viewport, ../hooks/use-tool-workshop, ../components/features/tool-workshop/ToolTypeListSidebar, ../components/features/tool-workshop/ToolTypeEditorPanel, ../components/features/tool-workshop/ToolWorkshopMobileView, react, use-mobile-viewport, use-tool-workshop, data-states, ToolTypeListSidebar, ToolTypeEditorPanel, ToolWorkshopMobileView
- WorkspaceDetailPage.test.tsx | Tests the WorkspaceDetailPage component rendering and tab switching behavior | dep: @testing-library/jest-dom/vitest, @testing-library/react, react-router-dom, vitest, ./WorkspaceDetailPage, @testing-library/jest-dom, WorkspaceDetailPage, use-workspaces, use-workspace-files, use-workspace-git, use-workspace-instances, use-mobile-viewport
- WorkspaceDetailPage.tsx | Renders a workspace detail page with tab-based navigation for files, git, tools, and settings panels, with mobile-responsive layout. | exp: func:WorkspaceDetailPage(), call:useParams, call:useState, call:useMobileViewport, call:useWorkspaces, call:workspaces.find | dep: react, react-router-dom, ../hooks/use-workspaces, ../hooks/use-mobile-viewport, ../components/features/workspace/workspace-detail-header, ../components/features/workspace/workspace-tab-bar, ../components/features/workspace/workspace-file-panel, ../components/features/workspace/workspace-git-panel, ../components/features/workspace/workspace-tools-panel, ../components/features/workspace/workspace-settings-panel, use-workspaces, use-mobile-viewport, workspace-detail-header, workspace-tab-bar, workspace-file-panel, workspace-git-panel, workspace-tools-panel, workspace-settings-panel
- WorkspacesPage.tsx | Renders a responsive workspaces management page with separate mobile (list/detail/create views) and desktop (grid with cards) layouts, supporting CRUD operations and tool launching. | exp: func:WorkspacesPage(), call:useMobileViewport, call:useState, call:useWorkspaces, call:useWorkspaceActions, call:actions.delete, call:actions.sync, call:setMobileView, call:refresh, call:setStartWorkspace, call:handleDelete, call:setSelectedWorkspace, call:workspaces.map, call:workspaces.find, call:e.stopPropagation, call:setShowCreate | dep: react, ../components/icon, ../hooks/use-mobile-viewport, ../hooks/use-workspaces, ../hooks/use-workspace-actions, ../components/features/workspace/workspace-card, ../components/features/workspace/workspace-create-form, ../components/features/mobile/mobile-list-view, ../components/features/mobile/mobile-detail-view, ../components/features/mobile/mobile-fab, ../components/features/tool/tool-starter, ../types/workspace
- WorkspacesPage.tsx | Renders a responsive workspaces management page with separate mobile and desktop layouts, supporting workspace listing, creation, deletion, sync, and tool launching. | exp: func:WorkspacesPage(), call:useMobileViewport, call:useState, call:useWorkspaces, call:useWorkspaceActions, call:actions.delete, call:actions.sync, call:setMobileView, call:refresh, call:setStartWorkspace, call:handleDelete, call:setSelectedWorkspace, call:workspaces.map, call:e.stopPropagation, call:setShowCreate | dep: react, ../components/icon, ../hooks/use-mobile-viewport, ../hooks/use-workspaces, ../hooks/use-workspace-actions, ../components/features/workspace/workspace-card, ../components/features/workspace/workspace-create-form, ../components/features/mobile/mobile-detail-view, ../components/features/mobile/mobile-fab, ../components/features/tool/tool-starter, ../types/workspace
## arch
Follows a React Router-based page-level architecture with responsive dual-layout patterns (mobile/desktop), tabbed navigation, CRUD operations with API integration, polling mechanisms, and nested outlet routing for complex settings/workspace detail views.
Follows a page-based routing architecture where each file maps to a URL route, using responsive design patterns with explicit mobile/desktop view branching, compound component layouts (sidebar/editor, tabbed panels), polling for real-time data, and direct API integration within page components rather than abstracted service layers.
## tags
page, components, workspace, features, react, mobile, settings, hooks
## symbols
+27 -1
View File
@@ -168,8 +168,34 @@ export const ProjectsPage = () => {
items={projects.map((p) => ({
id: p.id,
title: p.name,
subtitle: `${p.repositories?.length ?? 0} repo${(p.repositories?.length ?? 0) !== 1 ? "s" : ""}${p.description ? " · " + p.description : ""}`,
}))}
renderItem={(item) => {
const project = projects.find((p) => p.id === item.id);
if (!project) return null;
return (
<div className="mobile-list-item-content mobile-list-item-content-rich">
<div className="mobile-list-item-title">{project.name}</div>
{project.description && (
<p className="mobile-list-item-description">
{project.description}
</p>
)}
<div className="mobile-list-item-chips">
{(project.repositories?.length ?? 0) === 0 ? (
<span className="mobile-list-item-chip muted">
No repositories
</span>
) : (
project.repositories.map((repo) => (
<span key={repo.id} className="mobile-list-item-chip">
{repo.name}
</span>
))
)}
</div>
</div>
);
}}
onItemClick={(id) => {
const project = projects.find((p) => p.id === id);
if (project) {
+12 -17
View File
@@ -7,7 +7,6 @@ import { useWorkspaces } from "../hooks/use-workspaces";
import { useWorkspaceActions } from "../hooks/use-workspace-actions";
import { WorkspaceCard } from "../components/features/workspace/workspace-card";
import { WorkspaceCreateForm } from "../components/features/workspace/workspace-create-form";
import { MobileListView } from "../components/features/mobile/mobile-list-view";
import { MobileDetailView } from "../components/features/mobile/mobile-detail-view";
import { MobileFAB } from "../components/features/mobile/mobile-fab";
import { ToolStarter } from "../components/features/tool/tool-starter";
@@ -100,22 +99,18 @@ export function WorkspacesPage() {
{loading && workspaces.length === 0 ? (
<div className="loading-state">Loading workspaces...</div>
) : (
<MobileListView
items={workspaces.map((ws) => ({
id: ws.id,
title: ws.name,
subtitle: `${ws.project_name} · ${ws.name}`,
status: ws.status,
}))}
onItemClick={(id) => {
const ws = workspaces.find((w) => w.id === id);
if (ws) {
setSelectedWorkspace(ws);
setMobileView("detail");
}
}}
emptyMessage="No workspaces yet"
/>
<div className="workspaces-grid mobile-workspaces-list">
{workspaces.map((ws) => (
<WorkspaceCard
key={ws.id}
workspace={ws}
loading={actions.loadingId === ws.id}
onStartTool={setStartWorkspace}
onSync={handleSync}
onDelete={handleDelete}
/>
))}
</div>
)}
<MobileFAB
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/web/src/styles
## role
Provides the complete visual design system and styling foundation for the web application, encompassing global styles, theme tokens, syntax highlighting, and utility classes.
Provides the complete visual design system and styling foundation for the web application, encompassing global styles, design tokens, utility classes, and component-specific styles.
## parent
index: apps/web/src/.pi-map.index.md
map: apps/web/src/.pi-map.md
+4 -4
View File
@@ -4,16 +4,16 @@ dir: apps/web/src/styles
index: apps/web/src/styles/.pi-map.index.md
## role
Provides the complete visual design system and styling foundation for the web application, encompassing global styles, theme tokens, syntax highlighting, and utility classes.
Provides the complete visual design system and styling foundation for the web application, encompassing global styles, design tokens, utility classes, and component-specific styles.
## files
- global.css | Defines global CSS styles for a web application shell layout, navigation, cards, forms, dialogs, settings pages, and responsive design patterns. | dep: CSS custom properties (CSS variables: --border, --panel, --brand, --muted, --ink, --bg, --danger, --success, --warning, --font-size-xs, --font-size-sm, --space-2, --space-3, --space-4, --space-5)
- syntax-highlight.css | Stylesheet for a syntax highlighting component with toolbar, line numbers, code display, and Prism.js theme integration | dep: Prism.js
- tokens.css | Defines a comprehensive CSS design token system with light/dark themes, spacing scales, breakpoints, and fluid typography for a web application.
- utilities.css | Provides a comprehensive responsive CSS utility system including layout primitives, mobile-first breakpoints, terminal styling, session management UI, and touch-target accessibility patterns for a web application. | dep: CSS custom properties (var(--space-*), var(--border), var(--bg), var(--primary), var(--danger), etc.), xterm.js (terminal rendering)
- utilities.css | Provides responsive CSS utility classes and component-specific styles for a web application featuring terminals, dialogs, tables, forms, and navigation with mobile-first breakpoints | dep: CSS custom properties (variables like --space-*, --border, --bg, --brand, --muted, --success, --danger, --text-xs, --text-sm), xterm.js (terminal library)
## arch
CSS custom properties-based design token architecture with light/dark theme support, mobile-first responsive breakpoint system, utility-first CSS patterns, and modular separation of concerns across global, component-specific, token, and utility layers.
CSS custom properties (variables) based theming system with light/dark mode support, mobile-first responsive breakpoints, fluid typography scales, utility-first class patterns, and modular separation of concerns across tokens, utilities, global layouts, and component-specific styles.
## tags
space, var(, global, css, web, application, syntax, defines
space, global, css, web, application, syntax, defines, styles
## symbols
-
## workflows
+1 -1
View File
@@ -2,7 +2,7 @@
dir: apps/web/src/styles/pages
## role
Contains page-specific CSS stylesheets for individual application views in the web frontend.
Contains page-specific CSS stylesheets for individual application views in the web frontend, providing scoped styling for distinct feature areas.
## parent
index: apps/web/src/styles/.pi-map.index.md
map: apps/web/src/styles/.pi-map.md
+3 -3
View File
@@ -4,16 +4,16 @@ dir: apps/web/src/styles/pages
index: apps/web/src/styles/pages/.pi-map.index.md
## role
Contains page-specific CSS stylesheets for individual application views in the web frontend.
Contains page-specific CSS stylesheets for individual application views in the web frontend, providing scoped styling for distinct feature areas.
## files
- git-history.css | Stylesheet for a Git history visualization page with commit list, branch selector, and detail panel layout | dep: CSS custom properties (var(--border), var(--panel), var(--ink), var(--brand), var(--muted))
- projects.css | Stylesheet for a projects page UI featuring expandable project toggles, repository listings, workspace grids, and repository creation radio controls. | dep: CSS custom properties (variables)
- sessions.css | Stylesheet for session management UI components including cards, dropdowns, forms, and responsive mobile layouts. | dep: CSS custom properties/variables (var(--border), var(--panel), var(--text), var(--space-*), var(--danger), var(--color-border))
- ssh-keys.css | Styles a responsive SSH key list component that stacks vertically on mobile and switches to horizontal layout on larger screens | dep: CSS custom properties (var(--space-3), var(--space-2), var(--space-4), var(--bg), var(--border))
- workspace-detail.css | Stylesheet for a workspace detail page with tabbed interface including files, git, tools, and settings views, plus a workspace creation form | dep: CSS custom properties (variables for colors, spacing, typography)
- workspaces.css | Stylesheet for a workspaces page featuring a responsive grid of workspace cards with status badges, hover effects, and mobile adaptations. | dep: CSS custom properties (var(--space-*), var(--border), var(--panel), var(--muted), var(--success), var(--warning), var(--danger))
- workspaces.css | Stylesheet for a workspaces management page featuring responsive grid layout, workspace cards with status badges, instance chips, and mobile adaptations. | dep: CSS custom properties (design tokens: --space-*, --border, --panel, --brand, --ink, --muted, --bg, --success, --warning, --danger, --font-size-*)
## arch
Modular page-scoped styling using dedicated CSS files per route/page, with responsive design patterns (mobile-first breakpoints, grid/flex layouts) and component-oriented class naming.
Modular CSS architecture with page-level style separation, responsive design patterns using media queries for mobile adaptation, and component-scoped styling organized by feature domain rather than shared component library.
## tags
var(, workspace, stylesheet, space, git, page, detail, history
## symbols
+28 -10
View File
@@ -40,7 +40,6 @@
.workspace-card-top {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: var(--space-3);
}
@@ -63,15 +62,11 @@
min-width: 0;
}
.workspace-project-name {
font-size: var(--font-size-sm);
font-weight: 500;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 0.04em;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
.workspace-title-row {
display: flex;
align-items: center;
gap: var(--space-2);
min-width: 0;
}
.workspace-name {
@@ -82,6 +77,24 @@
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex: 1;
min-width: 0;
}
.workspace-project-name {
display: inline-flex;
align-items: center;
gap: var(--space-1);
font-size: var(--font-size-sm);
font-weight: 500;
color: var(--muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.workspace-project-name .icon {
color: var(--brand);
}
/* Body: meta row + instance chips */
@@ -233,4 +246,9 @@
width: 100%;
justify-content: center;
}
.mobile-workspaces-list {
gap: var(--space-3);
margin-top: 0;
}
}
+156 -4
View File
@@ -553,6 +553,12 @@ a.nav-item,
overflow: hidden;
}
/* Let xterm.js's own viewport handle touch; we only lock the outer page */
.terminal-container .xterm-viewport {
touch-action: auto;
overscroll-behavior: auto;
}
/* xterm.js manages its own positioning and sizing */
.terminal-container canvas {
@@ -566,6 +572,11 @@ a.nav-item,
padding: 0;
}
.terminal-wrapper.mobile {
touch-action: none;
overscroll-behavior: none;
}
/* Ensure xterm viewport fills container properly */
/* Responsive terminal */
@media (max-width: 767px) {
@@ -855,17 +866,23 @@ a.nav-item,
Mobile Terminal Overlay
============================================ */
/* Mobile terminal page — no padding, terminal fills viewport */
/* Mobile terminal page — no padding, terminal fills viewport, locked in place */
.terminal-page.mobile {
padding: 0;
gap: 0;
height: 100vh;
height: 100dvh;
position: relative;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow: hidden;
touch-action: none;
overscroll-behavior: none;
}
/* Overlay status bar — floats over terminal, never resizes it */
/* Overlay status bar — floats over terminal; only its buttons consume pointer events */
.mobile-terminal-overlay {
position: absolute;
top: 0;
@@ -874,11 +891,21 @@ a.nav-item,
z-index: 100;
background: #2d2d2d;
border-bottom: 1px solid #3e3e3e;
pointer-events: none;
transition:
transform 0.3s ease,
opacity 0.3s ease;
}
.mobile-terminal-overlay.visible {
pointer-events: auto;
}
.mobile-terminal-overlay > *,
.mobile-terminal-overlay-tabs {
pointer-events: auto;
}
.mobile-terminal-overlay.hidden {
transform: translateY(-100%);
opacity: 0;
@@ -978,13 +1005,15 @@ a.nav-item,
border-bottom: none;
}
/* Terminal content — always fills full viewport on mobile */
/* Terminal content — always fills full viewport on mobile, locked against page scroll */
.terminal-page-content.mobile-full {
flex: 1;
min-height: 0;
border: none;
border-radius: 0;
overflow: hidden;
touch-action: none;
overscroll-behavior: none;
}
/* Mobile fullscreen */
@@ -1957,6 +1986,129 @@ a.nav-item,
color: var(--brand);
}
/* Mobile List View */
.mobile-list-view {
display: flex;
flex-direction: column;
gap: var(--space-3);
}
.mobile-list-items {
display: flex;
flex-direction: column;
gap: var(--space-2);
}
.mobile-list-item {
display: flex;
align-items: center;
gap: var(--space-3);
width: 100%;
padding: var(--space-3);
background: var(--panel);
border: 1px solid var(--border);
border-radius: 10px;
color: var(--text);
text-align: left;
cursor: pointer;
min-height: 56px;
}
.mobile-list-item:active {
background: var(--bg);
}
.mobile-list-item-icon {
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
color: var(--muted);
}
.mobile-list-item-content {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: var(--space-1);
}
.mobile-list-item-title {
font-weight: 600;
font-size: var(--font-size-base);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.mobile-list-item-subtitle {
font-size: var(--font-size-sm);
color: var(--muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.mobile-list-item-actions {
display: flex;
align-items: center;
flex-shrink: 0;
color: var(--muted);
}
.mobile-list-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: var(--space-3);
padding: var(--space-10) var(--space-4);
color: var(--muted);
text-align: center;
}
.mobile-list-empty p {
margin: 0;
}
/* Rich project rows */
.mobile-list-item-content-rich {
gap: var(--space-2);
}
.mobile-list-item-description {
margin: 0;
font-size: var(--font-size-sm);
color: var(--muted);
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.mobile-list-item-chips {
display: flex;
flex-wrap: wrap;
gap: var(--space-1);
margin-top: var(--space-1);
}
.mobile-list-item-chip {
font-size: var(--font-size-xs);
padding: var(--space-1) var(--space-2);
background: var(--bg);
border: 1px solid var(--border);
border-radius: 999px;
color: var(--ink);
}
.mobile-list-item-chip.muted {
color: var(--muted);
font-style: italic;
background: transparent;
}
/* Mobile Page Header */
.mobile-page-header {
display: flex;