Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .macroscope/check-run-agents/effect-service-conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,18 @@ Review changed TypeScript for the conventions below. They apply when a pull requ
- Use plain `make` and `layer` in a module named for its implementation (`BunPtyAdapter.ts`). Keep implementation-specific names when one abstract port module holds several implementations (`makeCloudflaredRelayClient`, `layerCloudflared` in `RelayClient.ts`). `infra/relay/src/db.ts` may keep its inline `Layer.succeed(RelayDb, db)`.
- When a service moves, delete the old files and update every consumer, including orchestration, MCP, tests, and integration harnesses. Do not leave compatibility re-export shims.

## Effect functions and tracing

- Reusable functions use named `Effect.fn("Service.operation")`, not functions wrapping
`Effect.gen`. Inline programs, callbacks, and layer construction can keep `Effect.gen`.
- Named `Effect.fn` creates a span; the unnamed form does not. Default to named spans for service
operations and I/O. `Effect.fnUntraced` is deliberate for per-chunk/per-event work, tight loops,
or helpers duplicating an existing tracing boundary; require a nearby reason for new uses.
- Preserve existing span names/options and avoid adding duplicate spans. Pass combinators after
the generator body; transforms receive the original function arguments after the effect.
- Preserve generic signatures, laziness, resource scope, and interruption behavior. An explicit
generator return type is `Effect.fn.Return<A, E, R>`.

## Dependency acquisition and runtime boundaries

- Production service construction acquires its Effect dependencies from the environment with `yield* Foo.Foo`, and `make`/`layer` types expose those requirements. Flag a factory that takes `Foo["Service"]` (or an object of Effect-returning methods) as a parameter when that value is a service dependency. Passing service instances explicitly in tests is fine; passing pure configuration, immutable domain values, or deliberate callback strategies is not service injection.
Expand Down
6 changes: 6 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,12 @@ Architecture and its constraints: `docs/internals/overview.md`. Glossary: `docs/
- Client mutations use `createEnvironmentRpcCommand`. Add protected methods to `CLIENT_GUARDED_RPC_SCOPES` in contracts and use the command's `permissionAtom` for UI availability. Grants are checked at execution against the destination environment; the server remains authoritative. Keep raw RPC clients inside `rpc/`, and extend the permission behavior tests when adding a protected method.
- Server features are services; transports stay thin. A `ws.ts` RPC handler, HTTP route, or MCP tool decodes input, calls one service method, and maps errors. See [Effect services](docs/internals/effect-services.md).
- `apps/web/src/components/ui` exports own their look. Pick a `variant` or `size`; do not restyle one with `className`. If none fits and the look is a generic concept, add a variant to the component; a look that belongs to one feature stays in that feature's own component, not in `components/ui`. Layout classes (width, flex, margin, position) belong on the parent. `shadcn/no-restyle` fails lint on violations. See [Web UI](docs/internals/web-ui.md).
- Reusable Effect functions use `Effect.fn("Service.operation")` with a stable span name, not a
function wrapping `Effect.gen`. Keep `Effect.gen` for inline programs and layer construction.
Use `Effect.fnUntraced` only when spans would be too frequent (stream chunks, per-event
projection, tight loops) or duplicate an existing tracing boundary; explain that choice nearby.
Named `Effect.fn` creates a span; the unnamed form does not. Preserve existing span names and
options, and pass combinators as additional function-builder arguments.
- Inferred types over annotations. `any` is the enemy.
- Comments say why: a constraint, an external quirk, or an invariant the code can't show. Don't restate names or signatures, narrate steps, or record history. `t3code/no-low-value-comments` catches the mechanical cases.
- Our users drive agents all day and notice a dropped frame, a lying spinner, and a stale label. No continuously repainting animations; they peg the GPU on high-refresh displays.
Expand Down
16 changes: 16 additions & 0 deletions docs/internals/effect-services.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,22 @@ export const layer = Layer.effect(Foo, make);
- **Tests** exercise behavior through the service, with test layers only for external dependencies.
Don't mock the logic under test.

## Effect functions and tracing

Reusable functions use `Effect.fn("Service.operation")`, not an arrow or declaration returning
`Effect.gen`. The named form creates a span; `Effect.fn(function* ...)` only captures stack
frames. Keep `Effect.gen` for inline programs, callbacks, and layer construction.

Default to named spans for service operations and I/O. Use `Effect.fnUntraced` deliberately for
per-chunk/per-event work, tight loops, or helpers already covered by the same operation's span.
Explain the reason beside the function or a group of related functions. A loop is not by itself
an exemption: its outer operation can still have a useful span without tracing each iteration.
Preserve existing span names and options instead of adding a second span for the same operation.

Pass effect combinators after the generator body. A transform that needs the function's input
receives it after the effect. Use `Effect.fn.Return<A, E, R>` when the generator needs an explicit
return type, and preserve generic parameters, laziness, scope, and interruption behavior.

## Runtime boundaries

`ManagedRuntime.make`, `runPromise`, and `runPromiseExit` belong at application and framework
Expand Down
Loading