docs: restructure server and web documentation into ADR files and a

routing table
This commit is contained in:
2026-07-17 22:07:55 +08:00
parent f8494a44ca
commit 519aa35f1f
23 changed files with 710 additions and 654 deletions
+19 -12
View File
@@ -1,19 +1,26 @@
# Docs
# Documentation
Documentation is organized by project. Use `server/` for the Go backend and `web/` for the browser client.
Read the documents required by the current task. Current-state documents, roadmaps, and decision records serve different purposes.
## Shared
| Document | Purpose | Read when |
|----------|---------|-----------|
| [`writing-guide.md`](writing-guide.md) | Documentation roles and writing rules | Writing or reviewing repository documentation |
## Server
| File | Content |
|------|---------|
| `server/architecture.md` | Module layout, package boundaries |
| `server/decisions.md` | Technical decisions (ADR) |
| `server/roadmap.md` | Feature progress and status |
| `server/development.md` | Build, test, debug workflow |
| Document | Purpose | Read when |
|----------|---------|-----------|
| [`server/architecture.md`](server/architecture.md) | Current packages, responsibilities, and enforced boundaries | Changing server structure or cross-package behavior |
| [`server/development.md`](server/development.md) | Build, test, review, and configuration workflow | Developing or debugging the server |
| [`server/roadmap.md`](server/roadmap.md) | Available, planned, and future capabilities | Planning or completing server features |
| [`server/decisions.md`](server/decisions.md) | Server ADR index | Making or revisiting a significant server decision |
## Web
| File | Content |
|------|---------|
| `web/decisions.md` | Technical decisions (ADR) |
| `web/roadmap.md` | Product boundary, foundation, planned structure, and deferred dependencies |
| Document | Purpose | Read when |
|----------|---------|-----------|
| [`../web/README.md`](../web/README.md) | Web development and browser workflow | Developing or debugging the Web client |
| [`web/roadmap.md`](web/roadmap.md) | Available, planned, and future capabilities | Planning or completing Web features |
| [`web/decisions.md`](web/decisions.md) | Web ADR index | Making or revisiting a significant Web decision |
+81 -101
View File
@@ -1,106 +1,86 @@
# Architecture
# Server Architecture
## Layered Design
## Request Flow
```
Handler (Gin handlers) ← translates HTTP ↔ Service calls
Service (business logic) ← orchestrates, authorizes, validates
↓ ↓
Repository (GORM data access) Storage (file I/O)
↓ ↓
[SQLite / PostgreSQL] [Local FS / S3]
```text
HTTP request
-> middleware
-> handler
-> service
-> repository and storage
```
Rules:
- Handler has no business logic — parse request, call service, write response.
- Handler and middleware never access repositories directly; they depend on services.
- Service has no HTTP awareness — operates on domain models and interfaces.
## Layer Responsibilities
| Layer | Packages | Responsibility |
|-------|----------|----------------|
| CLI | `cmd` | Register Cobra commands and start the application |
| Composition | `internal/app` | Open runtime resources and construct services |
| HTTP | `internal/server`, `internal/handler`, `internal/middleware`, `internal/api` | Route requests, authenticate principals, map DTOs, and write HTTP responses |
| Business | `internal/service`, `internal/model` | Validate operations, enforce authorization, and represent domain results and errors |
| Data | `internal/repository`, `internal/storage` | Persist metadata and file content |
| Infrastructure | `internal/config`, `internal/logging`, `internal/auth` | Load configuration, create loggers, and provide token and password primitives |
| Test support | `internal/testutil` | Prepare privileged fixtures through test-only helpers |
Handlers define REST request and response DTOs. `internal/api` maps domain error kinds to HTTP status codes and error bodies.
Services use protocol-neutral domain types and errors. Each service constructor accepts the repository capabilities required by its use cases.
Repository query capabilities return domain records. Mutation capabilities use operation-specific parameter types, select writable columns explicitly, and apply the ownership and lifecycle predicates of the operation.
Storage backends manage staged and permanent file content. Repository records hold storage paths and file metadata.
## Runtime Composition
`app.Bootstrap` opens the configured database, runs migrations, creates repositories and local storage, constructs the services, and returns `app.WebApp`.
`server.NewRouter` creates a Gin engine and registers the public and protected route groups. `cmd/serve.go` loads configuration, initializes logging, bootstraps the application, and runs graceful shutdown.
`app.WebApp` owns the database lifecycle and exposes the services, storage backend, configuration, and build metadata required by HTTP setup.
## HTTP Boundaries
Global middleware runs in this order:
1. Request ID
2. Gin access logger
3. Gin recovery
Protected routes use `AuthRequired` to authenticate an access token and attach a service principal. Administrator routes add `AdminRequired` to verify the principal's administrator flag.
| Route group | Access | Capability |
|-------------|--------|------------|
| `/api/v1/version` | Public | Build metadata |
| `/api/v1/auth` | Public | Registration, login, refresh, and logout |
| `/api/v1/account` | Authenticated | Account and application passkeys |
| `/api/v1/files` | Authenticated | File and directory operations |
| `/api/v1/admin/users` | Administrator | User listing, lookup, and deletion |
The route declarations in `internal/server/routes_public.go` and `internal/server/routes_protected.go` are the source of truth for individual methods and paths.
## Persistence Commands
| Area | Mutation contract |
|------|-------------------|
| Users | Registration accepts `RegisteredUserParams` and creates an active non-admin user. Administration exposes a separate deletion command. |
| Sessions | Refresh sessions are created, atomically consumed, revoked by token hash, or removed by maintenance operations. |
| Files | Upload, directory creation, metadata update, and soft deletion use distinct parameter types and commands. |
| Credentials | Application passkeys have explicit creation, usage-recording, and revocation operations. |
Metadata updates can write `name`, `parent_id`, and GORM-managed `updated_at`. File ownership and active-state predicates scope user mutations.
## Transaction Boundaries
File child creation, movement, and directory deletion use one hierarchy transaction protocol. PostgreSQL locks active owned source and parent rows in sorted ID order. SQLite adds `_txlock=immediate` to the configured DSN and reserves the write transaction before reading hierarchy state.
Refresh rotation locks, returns, and deletes the session in one transaction. A refresh session can issue one replacement token pair.
Uploads write to a staging path before promotion. The service promotes validated content before creating the active file record and attempts to remove promoted content when the database create fails.
## Enforced Architecture Constraints
`internal/architecture_test.go` enforces these boundaries:
- `internal/model` and `internal/service` do not import `net/http`, Gin, or `internal/api`.
- JSON response DTOs live in HTTP packages. Domain and service result structs are not the public REST schema.
- Domain models may use `json:"-"` for defensive redaction of sensitive fields; this is not a REST response contract.
- Domain errors carry a protocol-neutral kind, a safe message, and an optional cause. HTTP status mapping happens only at the API boundary.
- Repository query ports remain entity-oriented, while mutation ports expose business commands with operation-specific parameter types. Services never pass a mutable domain entity to a generic update or delete method.
- Service constructors using split repository capabilities accept the smallest capability needed by that service. Their composition-time unions do not cross into business services.
- Repository mutations use explicit columns and lifecycle/ownership predicates. Full-entity `Save` is prohibited so a write cannot accidentally change protected fields or revive a soft-deleted record.
- Repository abstracts the database; Storage abstracts where bytes live, including staged upload promotion.
- `internal/server` is the composition root — wires all dependencies together.
## Package Map
| Layer | Package | Purpose | Status |
|-------|---------|---------|--------|
| **CLI** | `cmd` | Cobra root command | 🛠 WIP |
| | `cmd/serve.go` | `mygo serve` — wire deps, start HTTP | ✅ |
| | `cmd/config.go` | `mygo config` — config subcommand | 🛠 WIP |
| | `cmd/status.go` | `mygo status` — health check | 🛠 WIP |
| **Config** | `internal/config` | Viper load (YAML + env + flags), typed Duration config via built-in decode hook | ✅ |
| **App** | `internal/app` | Runtime dependency container and build metadata | 🛠 WIP |
| **HTTP** | `internal/server` | Gin router init, route registration (public/protected split), graceful shutdown | ✅ |
| | `internal/handler` | HTTP handlers (auth, file, admin, webdav...) | 🛠 WIP |
| | `internal/middleware` | Gin middleware (logger, jwt, cors, auth) | 🛠 WIP |
| **Business** | `internal/service` | Business logic: `AuthService`, `FileService`, `AdminService`; protocol-neutral results and errors | ✅ |
| | `internal/model` | Domain types (User, File, Credential, Session), protocol-neutral error kinds | ✅ |
| **Data** | `internal/repository` | Repository interfaces + GORM implementations (User, Session, File, Credential) | ✅ |
| | `internal/storage` | Storage backend interface + local disk impl, with staging and promotion | 🛠 WIP |
| **Util** | `internal/auth` | JWT sign/verify (HS256), token type discrimination (access/refresh), password hashing (bcrypt), app passkey tokens | ✅ |
| | `internal/api` | Unified JSON error response helpers and REST error-kind mapping | ✅ |
| | `internal/testutil` | Test-only fixture helpers for privileged setup that production service ports intentionally do not expose | ✅ |
## Persistence Write Boundaries
- User registration accepts `RegisteredUserParams`; the repository fixes new users to non-admin, active state. Administrative deletion is a separate capability.
- File creation, directory creation, metadata changes, and soft deletion use distinct commands. Metadata updates can only write `name`, `parent_id`, and GORM-maintained `updated_at`.
- Refresh sessions are created, atomically consumed, idempotently revoked, or removed by explicit maintenance operations. Refresh token rotation consumes a session at most once.
- Passkey creation fixes the credential type to the app-passkey type. Usage recording and revocation include credential-type and ownership conditions where applicable.
- `app.WebApp` contains runtime services, storage, database lifecycle, configuration, and metadata. Repository implementations remain local to bootstrap and are injected directly into the services that require them.
## File Hierarchy Transactions
File child creation, movement, and directory deletion share one parent-locking protocol. PostgreSQL locks active owned source and parent rows in sorted ID order with `SELECT ... FOR UPDATE`; directory deletion checks active children while holding the directory lock. SQLite connections add `_txlock=immediate` to the configured DSN so the transaction acquires its write reservation before reading hierarchy state. These rules prevent a concurrent mutation from creating an active child below a deleted directory.
## API Routes (v0)
```
GET /api/v1/version
POST /api/v1/auth/register
POST /api/v1/auth/login
POST /api/v1/auth/refresh
POST /api/v1/auth/logout
GET /api/v1/account
GET /api/v1/account/passkeys
POST /api/v1/account/passkeys
DELETE /api/v1/account/passkeys/:id
GET /api/v1/files
POST /api/v1/files # JSON creates directories; multipart uploads files. File upload parent_id is a query parameter.
GET /api/v1/files/:id
GET /api/v1/files/:id/content
PUT /api/v1/files/:id
DELETE /api/v1/files/:id
GET /api/v1/admin/users
GET /api/v1/admin/users/:id
PUT /api/v1/admin/users/:id
DELETE /api/v1/admin/users/:id
```
## Middleware Chain
Applied globally by `gin.Default()`: logger → recovery
Planned globally: cors
Applied to protected groups: auth (extract bearer token, delegate access-token authentication to `AuthService`, inject principal into gin.Context)
Applied to admin groups: admin (check authenticated principal from context)
## Server Responsibilities
- `cmd/serve.go` loads config, calls `app.Bootstrap` to initialize DB + services, builds the router, and starts the HTTP server.
- `app.WebApp` carries runtime dependencies and build metadata needed to assemble handlers.
- `internal/server` owns Gin router setup (`router.go`), route registration split into `routes_public.go` (public auth) and `routes_protected.go` (JWT-protected account).
- Each route group creates its own handler instance: `routes_public.go` creates `AuthHandler`, `routes_protected.go` creates `AccountHandler` — no shared handler state between public and protected routes.
- `RunWithGracefulShutdown` stops accepting new requests on termination and gives in-flight requests time to finish.
- `internal/handler` and `internal/middleware` do not import `internal/repository`.
- Repository implementations do not call GORM `Save`; mutation methods select writable columns explicitly.
+11 -146
View File
@@ -1,148 +1,13 @@
# Technical Decisions
# Server Decisions
## 2026-04-25: v0 Tech Stack & Architecture
Read the ADRs relevant to the current change. Accepted records define active decisions. Superseded records preserve history and link to current guidance.
**Context**: Project skeleton was created with only cobra CLI. We needed a concrete tech stack and package layout to begin implementation.
**Decisions**:
| Area | Choice | Rationale |
|------|--------|-----------|
| HTTP framework | Gin | Most widely adopted Go web framework, mature middleware ecosystem |
| ORM | GORM | SQLite-first dev, PostgreSQL option later; GORM abstracts dialect differences |
| Config management | Viper | YAML + env vars + CLI flags three-way merge, built for cobra integration |
| Database | SQLite (v0) → PostgreSQL (future) | SQLite zero setup for dev; repo interface isolates the switch |
| File storage | Local disk (v0) → S3 (future) | Backend interface (`internal/storage`) hides implementation |
| File identity | UUID | Distributed-friendly, no coordination needed; cost is negligible for file metadata |
| Token strategy | JWT, refresh token stored in DB | Enables server-side revocation (admin kick, logout-all-devices) |
| Pagination | OFFSET/LIMIT | Simple, sufficient for v0; migrate to cursor-based if needed |
| API response format | Direct JSON success bodies + unified error body | HTTP status codes carry request outcome; error body carries human-readable details |
**Architecture**: Four-layer model — Handler (Gin) → Service (business logic) → Repository (GORM data access) + Storage (file I/O). Each layer depends only on interfaces of the layer below.
**Consequences**:
- Handler layer has no business logic; Service layer is reusable across REST API, WebDAV, and future Nextcloud API.
- Repository interfaces keep DB swappable; future PostgreSQL implementation only needs a new package.
- Refresh token in DB adds a `sessions` table and a `repository.SessionRepository` interface.
- UUID dependency: `github.com/google/uuid` to be added.
- Gin middleware chain: default logger/recovery → cors → auth (route-group-scoped).
## 2026-04-27: Web API Foundation
**Context**: The project needed the first HTTP slice that can validate Gin wiring and provide a stable shape for future auth, file, and admin APIs.
**Decisions**:
| Area | Choice | Guidance |
|------|--------|----------|
| API versioning | All REST routes under `/api/v1` | Keep future REST handlers under the versioned group. |
| Initial public endpoint | `GET /api/v1/version` | Returns build metadata only; health/readiness endpoints need a separate security review. |
| Success responses | Direct JSON resource bodies | Use HTTP status codes as the request outcome signal. |
| Error responses | `{"error":{"message":"..."}}` | Add machine-readable error codes only when clients need stable branching behavior. |
| App composition | `internal/app.WebApp` | `cmd/serve.go` creates the app from config and build metadata, then passes it to router setup. |
| Router setup | `internal/server.NewRouter(*app.WebApp)` | Public routes (`routes_public.go`) and protected routes (`routes_protected.go`) split by auth boundary; `WebApp` serves as the unified dependency container. |
| Server lifecycle | `RunWithGracefulShutdown` | Preserve graceful shutdown while keeping command startup linear. |
| Default middleware | `gin.Default()` | Use default logger/recovery for the skeleton; add CORS/auth explicitly when their policies exist. |
**Consequences**:
- Version is build metadata from `internal/app/version.go`, not a config-file field.
- `app.WebApp` is the place to add future runtime services, storage, and app metadata incrementally; repository implementations stay inside composition setup.
- Request ID middleware is not part of the current foundation; add it only with a logging/tracing/error-correlation design.
## 2026-04-29: Auth Refinements
**Context**: Auth layer had three structural weaknesses — handler duplication, indistinguishable token types, and fragile config duration parsing.
**Decisions**:
| Decision | Guidance |
|----------|----------|
| One handler per route group | `AuthHandler` owns `/auth/*` (public); `AccountHandler` owns `/account/*` (protected). A route group maps 1:1 to a handler type. |
| JWT `type` claim | `Claims.Type` distinguishes access from refresh tokens. Middleware and service enforce the correct type at their respective boundaries. `ParseToken` does no type check — it verifies cryptographic validity only. |
| Default JWT secret hardening | The development placeholder `jwt.secret` is replaced with an ephemeral runtime secret during config loading. Production and multi-instance deployments must set a stable secret. |
| `time.Duration` in config structs | Config fields representing durations use `time.Duration` directly. Viper's built-in `StringToTimeDurationHookFunc` handles string→Duration conversion at unmarshal time. No accessor methods, no runtime parsing. Invalid values fail at startup via `Load()`. |
**Consequences**:
- Handlers are independently extensible (caching, rate limiting scoped per handler).
- Refresh tokens cannot authenticate API requests; access tokens cannot be used to issue new token pairs.
- The placeholder JWT secret is safe for local startup, but tokens signed with it are invalidated on restart.
- New duration config fields require zero boilerplate — declare as `time.Duration` in the struct.
## 2026-07-05: Staged File Uploads
**Context**: Multipart uploads previously used `ParseMultipartForm`, which parses the request before service-level size checks and may spill oversized requests to temporary disk. The file service also wrote directly to the long-term storage path and then attempted compensating deletes on upload failure.
**Decisions**:
| Decision | Guidance |
|----------|----------|
| Stream multipart requests | Handlers use `Request.MultipartReader()` instead of `ParseMultipartForm`/`FormFile`, so uploads are streamed into the service. |
| Optional upload limits | `storage.max_upload_size = 0` means unlimited. Positive values enable both HTTP body limiting and service-level file content limiting. |
| Staging before promotion | Storage backends write upload bytes to a staging path first, then promote the object to the long-term data path only after validation succeeds. |
| Promote before DB create | The service promotes the object before creating the active file record, preventing visible DB rows from pointing at missing objects. If DB creation fails after promotion, the service best-effort deletes the promoted object. |
| Upload parent location | Multipart upload `parent_id` is passed as a query parameter, keeping the multipart body focused on the file stream. |
| Service-layer upload errors | HTTP transport errors are converted at the handler boundary; the file service only handles domain errors such as oversized uploads. |
**Consequences**:
- Interrupted, malformed, and oversized uploads leave only staging objects, which are safe to clean by path prefix and age.
- Local storage promotes with `os.Rename` and falls back to copy/delete on cross-device `EXDEV`; future S3 storage can implement promotion with copy/delete while keeping business visibility controlled by the DB row.
- A DB failure after promotion can still leave a long-term orphan object, but it is not visible through the file API and can be cleaned independently.
## 2026-07-05: Protocol-Neutral Service Boundary
**Context**: The service and model layers carried HTTP status codes and JSON response tags, and some middleware and handlers accessed repositories directly. That made the business layer harder to reuse for future WebDAV and Nextcloud-compatible APIs.
**Decisions**:
| Decision | Guidance |
|----------|----------|
| Protocol-neutral errors | `model.AppError` uses an error `Kind`, safe message, and optional cause. REST status codes are mapped in `internal/api` only. |
| API log references | REST error responses may include `error.log_id` for server-recorded errors: all 5xx responses and 4xx responses with internal causes. |
| Service boundaries | Handlers and middleware depend on services, not repositories. `AuthService` authenticates access tokens into a principal; `AdminService` owns admin user operations. |
| DTO ownership | Service and model structs are internal/domain data. HTTP handlers assemble response DTOs with JSON tags. |
| Architecture enforcement | Package-level tests reject HTTP imports in model/service and repository imports in handlers/middleware. Targeted model tests verify defensive JSON redaction for sensitive fields. |
**Consequences**:
- REST remains a handler/API concern, and future protocols can reuse services without HTTP leakage.
- Error responses keep the same top-level shape, with optional `log_id` instead of embedding log references in the message.
- Admin and auth middleware behavior is testable through service contracts rather than database access.
## 2026-07-15: Conceal File Resource Existence
**Context**: Returning permission-denied errors for cross-user file access allowed authenticated callers to distinguish another user's resource from a missing resource. Idempotent success for deleting missing files also conflicted with a consistent not-found concealment policy.
**Decisions**:
| Decision | Guidance |
|----------|----------|
| Conceal ownership | File and parent-directory ownership failures return the same not-found kind and safe message as missing or deleted resources. |
| Strict delete result | Deleting an active owned file returns success once; missing, deleted, and cross-user targets return not found. |
| Atomic soft delete | Repository deletes update only active rows and return `model.ErrNotFound` when no row changes. |
**Consequences**:
- Authenticated file operations cannot use status codes or messages to determine whether another user's resource exists.
- A successful first delete returns `204 No Content`; repeated deletes return `404 Not Found` while remaining idempotent in effect.
- Unauthenticated requests remain `401 Unauthorized`, non-empty directory deletion remains `409 Conflict`, and permission-denied behavior outside the file boundary remains unchanged.
## 2026-07-16: Command-Scoped Persistence Mutations
**Context**: Generic `Repository.Update(*Entity)`, full-entity GORM `Save`, and broad delete methods exposed fields and lifecycle transitions that individual services were not authorized to perform. They also allowed a stale file entity to revive a soft-deleted row, left file parent checks vulnerable to concurrent deletion, and allowed two refresh requests to consume the same session.
**Decisions**:
| Decision | Guidance |
|----------|----------|
| Command-scoped writes | Every mutation has a use-case-specific method and parameter type. Mutable domain entities do not cross a service-to-repository write boundary. |
| Capability interfaces | Auth and admin services accept only the query and command capabilities they require. Wider user and session interfaces exist only for construction and repository-focused consumers. |
| Explicit mutation predicates | Updates name concrete columns and include ownership, credential type, and/or active-state predicates appropriate to the command. A zero-row guarded mutation maps to a domain not-found result. |
| Transactional file hierarchy | Child creation, movement, and directory deletion revalidate hierarchy state in the same transaction. PostgreSQL locks relevant rows in sorted ID order; SQLite DSNs force `_txlock=immediate`. |
| Atomic refresh consumption | Refresh rotates through one transaction that locks, returns, and deletes the session. A concurrent replay cannot issue a second token pair. Logout remains an idempotent revoke-by-hash command. |
| Test-only privilege setup | Tests elevate users through `internal/testutil`; no production role mutation is added merely to prepare fixtures. |
**Consequences**:
- Protected user, file, session, and credential fields are absent from ordinary mutation inputs, reducing illegal write paths at compile time.
- Deleted file rows cannot be revived by stale metadata updates, and successful hierarchy races cannot leave active orphan children.
- PostgreSQL relies on row-level locking while SQLite serializes these hierarchy and session write transactions from their first database operation. This favors correctness over concurrent SQLite writers.
- Adding a future profile, password, role, restore, or other mutation requires a named command and an explicit capability rather than extending a universal update method.
- REST routes, payloads, status codes, database schema, and dependency set remain unchanged.
| ADR | Date | Status | Decision |
|-----|------|--------|----------|
| [ADR-0001](decisions/0001-server-foundation.md) | 2026-04-25 | Accepted | Select the server foundation |
| [ADR-0002](decisions/0002-rest-api-foundation.md) | 2026-04-27 | Accepted | Establish the REST API foundation |
| [ADR-0003](decisions/0003-auth-contracts.md) | 2026-04-29 | Accepted | Define authentication contracts |
| [ADR-0004](decisions/0004-staged-file-uploads.md) | 2026-07-05 | Accepted | Stage uploads before publication |
| [ADR-0005](decisions/0005-protocol-neutral-services.md) | 2026-07-05 | Accepted | Keep service contracts protocol-neutral |
| [ADR-0006](decisions/0006-conceal-file-resources.md) | 2026-07-15 | Accepted | Conceal file ownership boundaries |
| [ADR-0007](decisions/0007-command-scoped-persistence.md) | 2026-07-16 | Accepted | Use command-scoped persistence mutations |
@@ -0,0 +1,27 @@
# ADR-0001: Select the Server Foundation
Status: Accepted
Date: 2026-04-25
## Context
MyGO needed a server stack that supports local development, relational metadata, configurable deployment, and multiple file storage backends.
## Decision
- Use Gin for HTTP routing and middleware.
- Use GORM with SQLite and PostgreSQL drivers.
- Use Viper for YAML and environment configuration, with a Cobra flag for the config file path.
- Use local file storage first and expose storage through `internal/storage`.
- Use UUIDs for file and domain identities.
- Use JWT access tokens and database-backed refresh sessions.
- Use offset and limit pagination for the initial API.
- Return direct JSON success bodies and a shared JSON error shape.
- Organize the server as Handler -> Service -> Repository and Storage.
## Consequences
- SQLite supports a zero-service local setup; PostgreSQL supports multi-instance deployments.
- Repository and storage interfaces isolate infrastructure choices from services.
- Database-backed refresh sessions support rotation and revocation.
- Offset pagination remains suitable for the current dataset size and API scope.
@@ -0,0 +1,24 @@
# ADR-0002: Establish the REST API Foundation
Status: Accepted
Date: 2026-04-27
## Context
The first HTTP slice needed stable versioning, response conventions, dependency composition, and server lifecycle behavior.
## Decision
- Place REST routes under `/api/v1`.
- Expose build metadata through `GET /api/v1/version`.
- Return resource bodies directly for successful requests.
- Return errors as `{"error":{"message":"..."}}` with optional diagnostic fields.
- Use `internal/app.Bootstrap` to construct runtime dependencies and return `app.WebApp`.
- Register public and protected routes in separate server functions.
- Stop accepting new requests during shutdown and allow in-flight requests to finish.
## Consequences
- Future REST endpoints share one version boundary and response convention.
- `app.WebApp` provides the services and metadata required by route setup.
- HTTP lifecycle behavior stays in `internal/server`.
@@ -0,0 +1,24 @@
# ADR-0003: Define Authentication Contracts
Status: Accepted
Date: 2026-04-29
## Context
Authentication requires distinct access and refresh behavior, stable duration parsing, and safe development defaults.
## Decision
- Use `AuthHandler` for public authentication routes and `AccountHandler` for authenticated account routes.
- Add a JWT `type` claim that distinguishes access and refresh tokens.
- Verify token type at the boundary that consumes the token.
- Store configuration durations as `time.Duration` values.
- Replace known development JWT placeholders with a random runtime secret during configuration loading.
## Consequences
- Access tokens authenticate protected requests.
- Refresh tokens rotate token pairs through database sessions.
- Invalid duration values fail during startup.
- Tokens signed with the generated development secret expire when the process restarts.
- Multi-instance deployments require a stable configured secret.
@@ -0,0 +1,24 @@
# ADR-0004: Stage Uploads Before Publication
Status: Accepted
Date: 2026-07-05
## Context
File uploads need streaming size enforcement and a clear publication boundary between stored content and visible metadata.
## Decision
- Stream multipart bodies with `Request.MultipartReader`.
- Interpret `storage.max_upload_size = 0` as unlimited and positive values as a byte limit.
- Write upload content to a staging path.
- Validate and promote staged content before creating the active database record.
- Pass multipart `parent_id` as a query parameter.
- Convert HTTP body errors in the handler and domain upload errors in the service.
## Consequences
- Upload size checks apply while the request streams.
- Interrupted and rejected uploads remain in the staging namespace.
- Local storage promotes with rename and uses copy and delete across filesystems.
- A failed database create can leave a promoted object; the service attempts cleanup and the object remains invisible to the file API.
@@ -0,0 +1,23 @@
# ADR-0005: Keep Service Contracts Protocol-Neutral
Status: Accepted
Date: 2026-07-05
## Context
MyGO exposes REST today and is designed to support additional protocols. Business services need contracts that transport adapters can reuse.
## Decision
- Services return domain results and `model.AppError` values.
- HTTP handlers own REST response DTOs.
- `internal/api` maps domain error kinds to REST responses.
- HTTP packages obtain application behavior through service contracts and authenticated principals.
- Architecture tests enforce the package dependency boundaries.
## Consequences
- Additional protocol adapters can reuse the existing services.
- Each protocol adapter defines its own response mapping.
- Service tests exercise business behavior without constructing HTTP requests.
- Error responses can include `error.log_id` for failures recorded by the server.
@@ -0,0 +1,22 @@
# ADR-0006: Conceal File Ownership Boundaries
Status: Accepted
Date: 2026-07-15
## Context
Authenticated callers must not infer the existence of files owned by another user from status codes or response messages.
## Decision
- Map missing, deleted, and cross-user file targets to the same not-found result.
- Apply the same rule to parent-directory lookups.
- Return success for one deletion of an active owned file.
- Return not found for repeated deletion and cross-user deletion.
- Guard soft deletion with ownership and active-state predicates.
## Consequences
- File responses conceal resource ownership from other authenticated users.
- The first successful deletion returns `204 No Content`; later attempts return `404 Not Found`.
- Non-empty directory deletion continues to return a conflict.
@@ -0,0 +1,29 @@
# ADR-0007: Use Command-Scoped Persistence Mutations
Status: Accepted
Date: 2026-07-16
## Context
Each business operation needs a persistence contract that exposes its writable fields, authorization predicates, and transaction boundary.
## Decision
- Give each mutation a use-case-specific method and parameter type.
- Inject the query and mutation capabilities required by each service.
- Select mutation columns explicitly.
- Include ownership, credential type, and active-state predicates where the operation requires them.
- Revalidate file hierarchy state inside the mutation transaction.
- Lock PostgreSQL hierarchy rows in sorted order.
- Start SQLite write transactions with `_txlock=immediate`.
- Consume a refresh session in one lock, read, and delete transaction.
- Use `internal/testutil` for privileged fixture setup.
## Consequences
- Mutation inputs expose the fields writable by the use case.
- Guarded zero-row updates map to domain not-found results.
- File hierarchy mutations preserve active parent-child relationships under concurrency.
- One refresh session issues one replacement token pair.
- SQLite serializes these write transactions from their first database operation.
- Future mutations require a named command and an explicit capability.
+55 -45
View File
@@ -1,9 +1,13 @@
# Development
# Server Development
## Prerequisites
- Go 1.26.2 (pinned in `mise.toml`)
- `mise` (https://mise.jdx.dev) — run `mise install` to install toolchain
- Go 1.26.2, pinned in `mise.toml`
- `mise` for installing the pinned toolchain
```bash
mise install
```
## Build
@@ -20,69 +24,75 @@ go test -v -run TestName ./internal/...
go test -race ./internal/repository ./internal/service ./internal/server
```
## Lint & Format
## Format and Static Analysis
```bash
go vet ./...
go fmt ./...
```
## Repository Review
Use the repository-scoped `$mygo-api-repo-review` skill for evidence-based architecture, design, security, resilience, data-consistency, performance, and implementation audits. Reviews are read-only unless fixes are requested separately.
Select one review mode:
- `diff` compares the current workspace, including relevant untracked files, with `HEAD`.
- `main` compares the current workspace with the merge base of `HEAD` and the local `main` branch without fetching remote refs.
- `full` reviews the current repository as a whole.
Optionally limit any mode to a target path, Go package, or logical component. For example:
```text
Use $mygo-api-repo-review in main mode with target internal/storage.
go vet ./...
```
## Dependencies
Run module cleanup after adding or removing imports:
```bash
go mod tidy # after adding/removing imports
go mod tidy
```
## Config
## Repository Review
Server config is loaded via viper from `config.yaml` (defaults in `internal/config/load.go`).
Use `$mygo-api-repo-review` for evidence-based architecture, design, security, resilience, data-consistency, performance, and implementation audits.
For SQLite, the repository preserves configured DSN parameters and adds
`_txlock=immediate`. Write transactions therefore reserve the database before
performing hierarchy or refresh-session reads, matching the locking assumptions
used by the repository commands. Keep this behavior when supplying a SQLite URI
such as `file:mygo.db?cache=shared`.
| Mode | Scope |
|------|-------|
| `diff` | Workspace, including relevant untracked files, compared with `HEAD` |
| `main` | Workspace compared with the merge base of `HEAD` and local `main` |
| `full` | Complete repository |
Reviews are read-only unless the user also requests fixes. A mode can target a path, Go package, or logical component.
## Configuration
The server reads `config.yaml` from the working directory when present. Environment variables use the `MYGO_` prefix and underscores, for example `MYGO_SERVER_PORT` and `MYGO_JWT_SECRET`.
```yaml
server:
host: 0.0.0.0
port: 10086
host: 0.0.0.0
port: 10086
database:
driver: sqlite3
sqlite:
path: data/mygo.db
driver: sqlite3
sqlite:
path: data/mygo.db
storage:
driver: local
local:
path: data/files
driver: local
max_upload_size: 0
local:
path: data/files
jwt:
secret: dev-secret-do-not-use-in-production
access_ttl: 15m
refresh_ttl: 168h
secret: dev-secret-do-not-use-in-production
access_ttl: 15m
refresh_ttl: 168h
log:
level: info
file_path: ""
file_level: debug
```
Environment variables use `MYGO_` prefix with underscore separators: `MYGO_SERVER_PORT=8080`, `MYGO_JWT_SECRET=...`
| Setting | Behavior |
|---------|----------|
| `database.driver` | Selects `sqlite3` or `postgres` |
| `storage.max_upload_size` | `0` allows any upload size; a positive byte count enables request and service limits |
| `jwt.secret` | Signs access and refresh tokens |
| `log.level` | Sets stderr logging to `debug`, `info`, `warn`, or `error` |
| `log.file_path` | Enables an additional text log file when set |
| `log.file_level` | Sets the file log level independently |
The default JWT secret is a development placeholder. At startup, MyGO replaces
it with an ephemeral runtime secret; set a stable `jwt.secret` or
`MYGO_JWT_SECRET` when tokens must survive restarts or when running multiple
instances.
PostgreSQL uses the `database.postgres` host, port, user, password, database name, and SSL mode fields defined in `internal/config`.
The SQLite connector preserves configured DSN parameters and sets `_txlock=immediate`. This setting establishes the transaction behavior required by hierarchy and refresh-session commands.
The default JWT secret is a development placeholder. Configuration loading replaces it with a random in-memory secret for the current process. Set a stable secret for multi-instance deployments and tokens that must survive a restart.
+22 -34
View File
@@ -1,40 +1,28 @@
# Roadmap
# Server Roadmap
## v0
## Available Capabilities
| Feature | Status | Notes |
|---------|--------|-------|
| CLI config management | ✅ | Viper YAML + env + flags, typed Duration config |
| JWT authentication | ✅ | access + refresh tokens, atomic single-use refresh sessions, app passkey support |
| Web API foundation | ✅ | WebApp composition, Gin router, graceful shutdown, `GET /api/v1/version` |
| File upload/download/manage APIs | 🛠 WIP | REST API via Gin |
| Admin endpoints | 🛠 WIP | user service boundary in place for superusers |
| WebDAV | 🛠 WIP | future v0 or v1 |
- YAML and environment configuration loading
- SQLite and PostgreSQL metadata persistence
- JWT access and single-use refresh tokens
- Account and application-passkey APIs
- File listing, upload, download, directory creation, metadata update, and soft deletion
- Ownership concealment for file resources
- Administrator user listing, lookup, and deletion
- Local storage with staged upload promotion
- Request IDs, structured logging, and graceful shutdown
- Architecture, repository, service, handler, and route integration tests
## Implementation Tasks
## Planned v0 Work
Package-level implementation order (each task includes unit tests):
- Add `mygo config` for instance configuration management.
- Add `mygo status` for server status inspection.
- Complete the remaining administrator workflows.
- Define the WebDAV delivery milestone and acceptance criteria.
1. `internal/config` — Viper loader, config struct ✅
2. `internal/app` — runtime dependency container ✅
3. `internal/model` — domain types, error codes ✅
4. `internal/api` — protocol-neutral error kind to REST response mapping ✅
5. `internal/auth` — JWT utils ✅
6. `internal/storage` — backend interface + local fs with staged upload promotion
7. `internal/repository` — command-scoped mutation capabilities + GORM/SQLite transaction protocol ✅
8. `internal/service` — auth, file, admin services ✅
9. `internal/middleware` — logger, cors, auth ✅ (auth done; principal boundary enforced)
10. `internal/handler` — auth, account, file, admin handlers 🛠 (HTTP DTO mapping in place)
11. `internal/server` — Gin router, route registration, graceful shutdown ✅
12. `cmd/serve.go`, `cmd/config.go`, `cmd/status.go` ✅ (serve done)
13. Integration tests 🛠 (authenticated register → login → upload → list → download route flow covered with a small file; authentication bypass, invalid token, concealed cross-user resources, strict delete, atomic refresh consumption, and file hierarchy race boundaries covered)
14. Architecture boundary tests ✅
## Future Candidates
## Future
| Feature | Status | Notes |
|---------|--------|-------|
| Image server | ⬜ plan | thumbnail generation |
| Pastebin & code snippets | ⬜ plan | in sharing context |
| S3 storage backend | ⬜ plan | new storage impl |
| Nextcloud-compatible API | ⬜ plan | new handler layer on existing services |
- S3 storage
- Image thumbnails
- Paste and code-snippet sharing
- Nextcloud-compatible protocol adapters
+9 -100
View File
@@ -1,102 +1,11 @@
# Technical Decisions
# Web Decisions
## 2026-07-14: Client-rendered Web Foundation
Read the ADRs relevant to the current change. Accepted records define active decisions. Superseded records preserve history and link to current guidance.
**Context**: MyGO needs a browser client now and native clients later. The Web application must share the versioned REST API instead of introducing browser-only server logic.
**Decisions**:
| Area | Choice | Guidance |
|------|--------|----------|
| Rendering | Pure client-side rendered SPA | Vite emits static assets; do not introduce SSR, React Server Components, or a Node API server. |
| Application stack | React, strict TypeScript, React Router, and TanStack Query | Keep routing and remote-data state explicit and client-side. |
| UI system | Ant Design plus Tailwind CSS 4 | Ant Design owns reusable controls and theme tokens; Tailwind initially owns layout, spacing, and responsive utilities. |
| Dependency policy | Install capabilities when their feature starts | Keep API generation, transfer, virtualization, drag-and-drop, test, and preview libraries deferred in `docs/web/roadmap.md`. |
**Consequences**:
- The Web and future native clients consume the same client-neutral API contracts.
- MyGO or a reverse proxy may host `web/dist` with an SPA fallback without changing the rendering model.
- MyGO domain components own file-browser behavior and must not depend on Ant Design request behavior for business logic.
## 2026-07-14: Browser Authentication and Root File Workflow
**Context**: The first Web milestone needs to exercise the existing login, file
list, upload, and download APIs without committing to the later directory,
account, admin, or large-transfer designs.
**Decisions**:
| Area | Choice | Guidance |
|------|--------|----------|
| API topology | Same-origin `/api/v1` | Vite proxies `/api` to the local Go server. Production uses a same-origin reverse proxy; the milestone does not add CORS or Go static hosting. |
| Browser session | Token pair in `sessionStorage` | Reloading the tab preserves the session, while closing the tab clears it. Do not add persistent login until the token transport design is revisited. |
| Token refresh | Refresh once after a protected request returns 401 | Share one in-flight refresh across concurrent failures, store the rotated pair, and retry each request once. Clear the session if refresh or the retry fails. |
| File scope | Root directory only | List root entries, upload one file to root, and download files. Show directories as non-interactive rows. |
| Transfer model | Browser `FormData` upload and authenticated Blob download | This is intentionally limited to the small-file milestone; progress, streaming-to-disk, chunking, resume, and queues remain deferred. |
**Consequences**:
- The API client owns bearer headers, error parsing, refresh coordination, and
session invalidation; pages consume operation-specific functions.
- TanStack Query caches are cleared whenever an authenticated session ends so
one user cannot see another user's cached file metadata.
- The file picker is independent of Ant Design upload request behavior, keeping
transfer policy in MyGO code.
- Handwritten TypeScript wire types remain temporary until an OpenAPI contract
is available.
## 2026-07-14: Project-local Headless Browser Tooling
**Status**: Agent-debugging and browser-version decisions were superseded by
the 2026-07-15 Playwright CLI decision below. The Playwright Test and
project-local resource decisions remain active.
**Context**: Browser behavior needs deterministic E2E coverage and interactive
agent debugging inside a long-lived headless Debian Incus container. Tooling
should remain reproducible without placing browser binaries or generated
artifacts in a developer's home directory.
**Decisions**:
| Area | Choice | Guidance |
|------|--------|----------|
| Regression tests | Playwright Test with bundled Chromium | Keep browser E2E tests separate from `npm run check` because browser installation is an explicit environment setup step. |
| Agent debugging | Official Playwright MCP over project-scoped STDIO | Codex starts the locked local package from `.codex/config.toml`; do not use a global install, an `@latest` npx invocation, or a listening MCP service. |
| Browser state | Headless, isolated, and sandboxed | Use bundled Chromium, discard the MCP profile after each session, and retain the Chromium sandbox supported by the Incus environment. |
| Local resources | Repository `.cache/` and `.artifacts/` directories | `mise.toml` defines the portable npm and browser cache paths. Generated resources remain ignored by Git. |
| Browser versions | Install both locked Playwright revisions | The stable test runner and current MCP package require different Chromium revisions; do not force either package onto an unsupported executable. |
**Consequences**:
- Debian browser libraries are installed once in the Incus container root
filesystem; npm packages, browsers, traces, screenshots, and MCP output stay
project-scoped.
- A fresh environment runs `npm ci`, the system dependency installer, and both
browser install scripts before browser tests or MCP debugging.
- Browser failures can retain traces, screenshots, and video under
`.artifacts/playwright/` without adding generated files to source control.
## 2026-07-15: Stable Playwright CLI Agent Debugging
**Context**: The official Playwright MCP package pulled an alpha Playwright
core alongside the stable test runner. That required a second Chromium
revision and expanded the agent tool surface. Playwright 1.61.1 already
provides the same browser-debugging command set through its embedded CLI.
**Decisions**:
| Area | Choice | Guidance |
|------|--------|----------|
| Agent entry point | Stable `playwright cli` behind the `playwright:cli` npm script | Run it from `web/` with `mise exec -- npm run playwright:cli --`; do not use a global install, standalone `@playwright/cli`, `playwright-cli`, or floating `npx`. |
| Version ownership | `@playwright/test` is the only Playwright dependency | E2E tests and interactive debugging use Playwright 1.61.1 and Chromium revision 1228. |
| Agent workflow | Repository Skill in `.agents/skills/playwright-cli` | Start from Playwright's generated Skill, then preserve the MyGO npm wrapper, headless workflow, artifact policy, and debugging principles when updating it. |
| Browser policy | Bundled Chromium, headless, isolated, and sandboxed | `.playwright/cli.config.json` is the shared CLI configuration; do not silently disable the Chromium sandbox. |
| Local resources | Repository `.cache/` and `.artifacts/` directories | `mise.toml` redirects XDG, npm, and browser caches; CLI output is limited to 100 MB under `.artifacts/playwright-cli/`. |
**Consequences**:
- A fresh environment needs one locked npm install, the Debian browser
libraries, and the stable Chromium install before E2E or CLI use.
- Changing Playwright's CLI entry point only requires changing the npm script;
the repository Skill and operator commands remain stable.
- The official generated references remain available through progressive
disclosure, while the main Skill carries only the MyGO-specific workflow.
- The MCP server configuration, alpha Playwright core, and Chromium revision
1232 are no longer required.
| ADR | Date | Status | Decision |
|-----|------|--------|----------|
| [ADR-0001](decisions/0001-client-rendered-foundation.md) | 2026-07-14 | Accepted | Use a client-rendered Web foundation |
| [ADR-0002](decisions/0002-browser-auth-root-files.md) | 2026-07-14 | Accepted | Implement browser auth and the root file workflow |
| [ADR-0003](decisions/0003-project-local-browser-testing.md) | 2026-07-14 | Accepted | Keep browser testing resources project-local |
| [ADR-0004](decisions/0004-playwright-mcp-debugging.md) | 2026-07-14 | Superseded | Use Playwright MCP for agent debugging |
| [ADR-0005](decisions/0005-playwright-cli-debugging.md) | 2026-07-15 | Accepted | Use the pinned Playwright CLI for agent debugging |
@@ -0,0 +1,24 @@
# ADR-0001: Use a Client-Rendered Web Foundation
Status: Accepted
Date: 2026-07-14
## Context
MyGO needs a browser client and expects native clients to consume the same application API.
## Decision
- Build a client-side application with Vite, React, and strict TypeScript.
- Use React Router for navigation and TanStack Query for remote state.
- Use Ant Design for reusable controls and theme tokens.
- Use Tailwind CSS for layout, spacing, and responsive utilities.
- Keep business API contracts client-neutral.
- Select additional dependencies when their feature is designed.
## Consequences
- Vite emits static assets for same-origin deployment.
- The Web project contains no SSR, React Server Components, or Node application server.
- Browser and native clients share the versioned REST API.
- MyGO components own file-browser and transfer behavior; Ant Design supplies presentation controls.
@@ -0,0 +1,26 @@
# ADR-0002: Implement Browser Auth and the Root File Workflow
Status: Accepted
Date: 2026-07-14
## Context
The first Web milestone needed an authenticated workflow over the existing login, file listing, upload, and download APIs.
## Decision
- Use same-origin `/api/v1` requests.
- Store the access and refresh token pair in `sessionStorage`.
- Coordinate one refresh request across concurrent `401` responses.
- Retry each failed protected request once after refresh.
- Clear the session when refresh or the retry fails.
- List root entries and upload one file at a time to the root directory.
- Download authenticated file responses as browser blobs.
## Consequences
- Reloading the tab preserves the session; closing the tab clears it.
- Query caches clear when the authenticated session ends.
- Directories appear as non-interactive root rows in the first milestone.
- Handwritten wire types remain until a shared OpenAPI contract is available.
- Directory management and large-transfer workflows require later feature decisions.
@@ -0,0 +1,22 @@
# ADR-0003: Keep Browser Testing Resources Project-Local
Status: Accepted
Date: 2026-07-14
## Context
Browser tests and diagnostics run in a headless Debian development container. The toolchain needs repeatable versions and repository-scoped resources.
## Decision
- Use Playwright Test with bundled Chromium for repeatable E2E tests.
- Keep E2E tests separate from `npm run check` because browser installation is an explicit setup step.
- Run Chromium headlessly with profile isolation and sandboxing.
- Store npm and browser caches under `.cache/`.
- Store traces, screenshots, videos, and CLI output under `.artifacts/`.
## Consequences
- A fresh environment installs npm dependencies, browser libraries, and the pinned Chromium revision before browser work.
- Browser failures can retain diagnostics without adding generated files to source control.
- Repository configuration controls browser versions and artifact locations.
@@ -0,0 +1,12 @@
# ADR-0004: Use Playwright MCP for Agent Debugging
Status: Superseded by [ADR-0005](0005-playwright-cli-debugging.md)
Date: 2026-07-14
## Historical Decision
The project initially selected a repository-scoped Playwright MCP process for interactive agent debugging.
## Current Guidance
[ADR-0005](0005-playwright-cli-debugging.md) defines the current browser-debugging entry point. [ADR-0003](0003-project-local-browser-testing.md) defines the browser test and resource policy that remains active.
@@ -0,0 +1,23 @@
# ADR-0005: Use the Pinned Playwright CLI for Agent Debugging
Status: Accepted
Date: 2026-07-15
## Context
The Playwright MCP package introduced a second Playwright core, a second Chromium revision, and a larger agent tool surface. The pinned test runner provides the required interactive commands through its CLI.
## Decision
- Use the `playwright:cli` npm script as the browser-debugging entry point.
- Use `@playwright/test` as the Playwright dependency for tests and diagnostics.
- Use the bundled Chromium revision selected by the pinned package.
- Load `.playwright/cli.config.json` for headless, isolated, and sandboxed execution.
- Use the repository-scoped `$playwright-cli` skill for agent browser work.
- Limit CLI output under `.artifacts/playwright-cli/`.
## Consequences
- E2E tests and interactive diagnostics share one Playwright core and Chromium revision.
- The npm script isolates operators and agents from upstream CLI entry-point changes.
- The repository skill keeps the main workflow concise and exposes detailed references on demand.
+26 -51
View File
@@ -1,65 +1,40 @@
# Web Roadmap
## Product Boundary
## Product Model
- The Web client is a pure client-side rendered single-page application.
- Vite produces static assets only. The project does not use SSR, React Server Components, a Node API server, or a browser-specific business backend.
- The Web client consumes the same versioned REST API as future Android and other native clients.
- Production may serve `web/dist` from MyGO or a reverse proxy.
- Shared API contracts should remain client-neutral and eventually be described by OpenAPI.
MyGO Web is a client-side application built by Vite. It consumes the same `/api/v1` REST API as native clients. Production can serve `web/dist` from MyGO or a same-origin reverse proxy.
## Current Milestone
## Available Capabilities
The browser client now provides the first authenticated file workflow:
- Email and password login
- Access and refresh tokens stored for the browser session
- One coordinated refresh retry after an authenticated request returns `401`
- Protected application shell
- Root file listing with 50 items per page
- Single-file upload to the root directory
- Authenticated file download
- Responsive Ant Design interface with Tailwind layout utilities
- Unit tests for the API client and session behavior
- Playwright smoke tests and repository-scoped browser diagnostics
- Email/password login through the shared REST API
- Session-scoped access and refresh tokens with one automatic refresh retry
- Protected application shell and root file list
- Single-file upload to the root directory and authenticated download
- Server-side pagination with 50 items per page
Directory navigation and management, multi-file queues, transfer progress,
resumable or chunked transfers, account/profile/settings screens, and admin
screens remain deferred.
## Foundation
The project foundation contains:
- Node.js 24 and npm
- Vite
- React
- TypeScript in strict mode
- TanStack Query provider
- Tailwind CSS 4 through its Vite plugin
- Ant Design provider and components
- Ant Design icons for application and file actions
- Oxlint from the Vite template
- Vitest for framework-independent client and session tests
- Playwright Test with a headless Chromium smoke test
- Project-scoped Playwright CLI and repository Skill for interactive browser debugging
Ant Design owns reusable UI components and theme tokens. Tailwind CSS is initially limited to application layout, spacing, and responsive utilities.
## Planned Structure
## Current Structure
```text
web/src/
├── app/ # Router, providers, and application composition
├── api/ # API client and generated contracts
├── api/ # API client, session storage, and wire types
├── app/ # Router, providers, and authentication guard
├── components/ # Shared presentation components
├── features/ # Auth, files, account, and admin features
── pages/ # Route entry points
├── lib/ # Framework-independent utilities
└── test/ # Shared test setup and fixtures
├── features/ # Feature hooks and state
── pages/ # Route entry points
```
## Deferred Dependencies
## Planned Capabilities
Some dependencies are suggested for future implementation. Refer these only when the corresponding feature is implemented and propose better choices if any:
- Directory navigation and management
- Account, settings, and administrator screens
- Shared OpenAPI contracts and generated client types
- Multi-file transfer queues and progress reporting
- Resumable large-file transfers
- Document preview
- `openapi-typescript`: Generate TypeScript API types from the shared OpenAPI document.
- `openapi-fetch`: Provide a small type-safe Fetch client based on generated OpenAPI types.
- `openapi-react-query`: Connect generated OpenAPI operations to TanStack Query if handwritten query adapters become repetitive.
- `zustand`: Manage a cross-route upload queue, bulk selection, or other complex client-only state if React state is insufficient.
- `pdfjs-dist`: Preview PDF files in the browser when document preview is implemented.
Select dependencies when designing the capability that requires them. Record significant choices in the Web decision log.
+65
View File
@@ -0,0 +1,65 @@
# Documentation Writing Guide
## Goals
Repository documentation must be accurate, concise, and useful to both people and coding agents.
## Document Roles
| Document type | Content |
|---------------|---------|
| README | Project entry point and runnable paths |
| Architecture | Current structure, responsibilities, and enforced boundaries |
| Development | Commands and operational procedures |
| Roadmap | Available, planned, and future capabilities |
| Architecture Decision Record (ADR) | One technical decision, its context, and its consequences |
| `AGENTS.md` | Durable agent workflow, conventions, verification, and constraints |
Keep each fact in its canonical document. Link to that document from other locations.
## Current-State Writing
- Use English, active voice, and present tense.
- Name the component that performs each action.
- Put one fact or instruction in each sentence or list item.
- Prefer short sections, lists, and tables.
- State the supported path before optional details.
- Use `must` for requirements, `can` for optional behavior, and present tense for existing behavior.
- Use current commands, symbols, routes, and examples.
## Constraints and Anti-Patterns
Keep a negative rule **only when** it prevents a current and plausible mistake with a meaningful cost. This includes:
- Tooling traps that reduce reproducibility or pollute context
- Architecture boundaries enforced by tests
- Security, authorization, and destructive-action limits
- Commit and dependency-change authorization
Place the rule near the affected workflow and provide the supported path. Keep one canonical copy when the same audience reads multiple documents.
Remove or move a negative statement when it describes a hypothetical design, repeats a positive contract, or explains an obsolete implementation. Preserve material history in the relevant ADR instead.
## Decision Records
- Record one logical decision per ADR.
- Put the status before the body.
- Describe requirements and forces in Context.
- State the selected design in Decision.
- Record current trade-offs in Consequences.
- Link superseded records to their replacement.
- Use past tense for historical implementation details.
## Review Checklist
1. Confirm the document type and audience.
2. Verify facts against code and configuration.
3. Remove duplicated current-state information.
4. Check every negative statement for a current action or decision.
5. Verify commands, examples, and local links.
6. Run `git diff --check`.
## References
- [OpenAI Codex best practices](https://learn.chatgpt.com/guides/best-practices)
- [Architectural Decision Records](https://adr.github.io/)