Skip to content
Open
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
108 changes: 108 additions & 0 deletions docs/.vitepress/theme/components/LighthouseProductView.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<template>
<figure class="gf-lighthouse" aria-labelledby="gf-lighthouse-caption">
<div class="gf-lighthouse__chrome">
<span></span><span></span><span></span>
<strong>Lighthouse</strong>
<em>connected</em>
</div>
<div class="gf-lighthouse__body">
<aside class="gf-lighthouse__rail" aria-label="Lighthouse views">
<b>Overview</b>
<span>Inspects</span>
<span>Routes</span>
<span>Queues</span>
<span>Schedules</span>
<span>Cache</span>
<span>Storage</span>
<span>Benchmarks</span>
</aside>
<div class="gf-lighthouse__workspace">
<div class="gf-lighthouse__identity">
<div><small>PROJECT</small><strong>photodrop</strong></div>
<div><small>APP</small><strong>app</strong></div>
<div><small>RUNTIME</small><strong>http</strong></div>
<span>live</span>
</div>
<div class="gf-lighthouse__grid">
<section class="gf-lighthouse__panel gf-lighthouse__panel--wide">
<header><strong>Recent inspects</strong><small>execution timeline</small></header>
<div class="gf-lighthouse__inspect">
<span class="is-method">GET</span><b>/-/health</b><span class="is-ok">OK</span><time>http</time>
</div>
<div class="gf-lighthouse__timeline" aria-label="Inspect event timeline">
<i></i><span>request started</span>
<i></i><span>route matched</span>
<i></i><span>response written</span>
</div>
</section>
<section class="gf-lighthouse__panel">
<header><strong>Routes</strong><small>registered</small></header>
<p><span class="is-method">GET</span><code>/-/health</code></p>
<p><span class="is-method is-post">POST</span><code>/api/photos</code></p>
</section>
<section class="gf-lighthouse__panel">
<header><strong>Runtime resources</strong><small>discovered</small></header>
<p><b>Queues</b><span>default</span></p>
<p><b>Schedules</b><span>registered</span></p>
<p><b>Storage</b><span>local</span></p>
</section>
</div>
</div>
</div>
<figcaption id="gf-lighthouse-caption">A code-native view of the shipped Lighthouse workspace. The available views follow the components compiled into the selected App.</figcaption>
</figure>
</template>

<style scoped>
.gf-lighthouse {
margin: 1.75rem 0 2.25rem;
overflow: hidden;
border: 1px solid color-mix(in srgb, var(--vp-c-divider) 78%, #ffb648 22%);
border-radius: 18px;
background: #100d13;
box-shadow: 0 26px 70px rgb(0 0 0 / 28%);
color: #eee9f2;
}
.gf-lighthouse__chrome { display: flex; align-items: center; gap: 7px; padding: 11px 14px; border-bottom: 1px solid #2b2431; background: #18131c; }
.gf-lighthouse__chrome > span { width: 8px; height: 8px; border-radius: 50%; background: #665c6d; }
.gf-lighthouse__chrome > span:first-child { background: #ff725e; }
.gf-lighthouse__chrome > span:nth-child(2) { background: #ffc24d; }
.gf-lighthouse__chrome > span:nth-child(3) { background: #5fcfa8; }
.gf-lighthouse__chrome strong { margin-left: 8px; font-size: 12px; letter-spacing: .08em; text-transform: uppercase; }
.gf-lighthouse__chrome em { margin-left: auto; color: #78dbb8; font-size: 11px; font-style: normal; }
.gf-lighthouse__body { display: grid; grid-template-columns: 142px minmax(0, 1fr); min-height: 390px; }
.gf-lighthouse__rail { display: flex; flex-direction: column; gap: 3px; padding: 17px 10px; border-right: 1px solid #2b2431; color: #9990a1; font-size: 11px; }
.gf-lighthouse__rail span, .gf-lighthouse__rail b { padding: 7px 10px; border-radius: 7px; }
.gf-lighthouse__rail b { background: #2b2231; color: #fff; }
.gf-lighthouse__workspace { min-width: 0; padding: 18px; background: radial-gradient(circle at 75% 5%, rgb(125 72 148 / 13%), transparent 38%), #100d13; }
.gf-lighthouse__identity { display: grid; grid-template-columns: 1.4fr 1fr 1fr auto; gap: 8px; margin-bottom: 12px; }
.gf-lighthouse__identity > div { padding: 9px 11px; border: 1px solid #312838; border-radius: 9px; background: #18141c; }
.gf-lighthouse__identity small { display: block; color: #84798c; font-size: 8px; letter-spacing: .16em; }
.gf-lighthouse__identity strong { display: block; margin-top: 2px; font-size: 12px; }
.gf-lighthouse__identity > span { align-self: center; padding: 4px 9px; border: 1px solid rgb(95 207 168 / 30%); border-radius: 99px; background: rgb(95 207 168 / 9%); color: #78dbb8; font-size: 10px; }
.gf-lighthouse__grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.gf-lighthouse__panel { min-width: 0; padding: 13px; border: 1px solid #312838; border-radius: 11px; background: rgb(27 22 31 / 92%); }
.gf-lighthouse__panel--wide { grid-row: span 2; }
.gf-lighthouse__panel header { display: flex; justify-content: space-between; gap: 8px; padding-bottom: 10px; border-bottom: 1px solid #2b2431; font-size: 11px; }
.gf-lighthouse__panel header small { color: #84798c; }
.gf-lighthouse__inspect { display: grid; grid-template-columns: auto 1fr auto auto; align-items: center; gap: 8px; margin-top: 11px; padding: 9px; border-radius: 8px; background: #100d13; font-size: 10px; }
.gf-lighthouse__inspect time { color: #84798c; }
.is-method { display: inline-flex; width: max-content; padding: 2px 5px; border: 1px solid rgb(86 170 255 / 35%); border-radius: 5px; color: #8dc6ff; font-size: 9px; font-weight: 700; }
.is-post { border-color: rgb(198 137 255 / 35%); color: #d4a8ff; }
.is-ok { color: #78dbb8; }
.gf-lighthouse__timeline { display: grid; grid-template-columns: 12px 1fr; gap: 13px 8px; margin: 18px 10px; color: #aaa1b1; font-size: 10px; }
.gf-lighthouse__timeline i { position: relative; width: 7px; height: 7px; margin-top: 3px; border: 2px solid #ffc24d; border-radius: 50%; }
.gf-lighthouse__timeline i:not(:last-of-type)::after { content: ''; position: absolute; top: 8px; left: 1px; width: 1px; height: 22px; background: #51405d; }
.gf-lighthouse__panel p { display: flex; align-items: center; gap: 8px; margin: 9px 0 0; color: #aaa1b1; font-size: 10px; }
.gf-lighthouse__panel p span:last-child { margin-left: auto; color: #84798c; }
.gf-lighthouse__panel code { overflow: hidden; color: #d8d0dd; font-size: 10px; text-overflow: ellipsis; white-space: nowrap; }
.gf-lighthouse figcaption { padding: 10px 14px; border-top: 1px solid #2b2431; color: #8f8596; font-size: 10px; line-height: 1.5; }
@media (max-width: 700px) {
.gf-lighthouse__body { grid-template-columns: 1fr; }
.gf-lighthouse__rail { flex-direction: row; overflow-x: auto; border-right: 0; border-bottom: 1px solid #2b2431; }
.gf-lighthouse__rail span, .gf-lighthouse__rail b { white-space: nowrap; }
.gf-lighthouse__identity { grid-template-columns: 1fr 1fr; }
.gf-lighthouse__grid { grid-template-columns: 1fr; }
.gf-lighthouse__panel--wide { grid-row: auto; }
}
</style>
12 changes: 12 additions & 0 deletions docs/.vitepress/theme/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -7358,3 +7358,15 @@ html:not(.dark) .gf-blog-visual {
--gf-ink: #FFFFFF;
--gf-ink-2: #A9A1B3;
}
/* Atlas uses its source-owned product banner while the framework guide remains the preferred user path. */
.vp-doc .gf-atlas-banner {
margin: 1.5rem 0 1.75rem;
}

.vp-doc .gf-atlas-banner img {
display: block;
width: 100%;
border: 1px solid var(--vp-c-divider);
border-radius: 16px;
box-shadow: 0 18px 48px rgb(0 0 0 / 18%);
}
2 changes: 2 additions & 0 deletions docs/.vitepress/theme/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import StarterKitOptions from './components/StarterKitOptions.vue'
import SitePreview from './components/SitePreview.vue'
import CodeFile from './components/CodeFile.vue'
import MakeCommandTabs from './components/MakeCommandTabs.vue'
import LighthouseProductView from './components/LighthouseProductView.vue'
import './custom.css'

/* The hero is imported STATICALLY on purpose. As an async component it
Expand Down Expand Up @@ -683,6 +684,7 @@ export default {
ctx.app.component('GoForjLiveTerminal', GoForjLiveTerminal)
ctx.app.component('CodeFile', CodeFile)
ctx.app.component('MakeCommandTabs', MakeCommandTabs)
ctx.app.component('LighthouseProductView', LighthouseProductView)
},
Layout: () => {
const { theme } = useData()
Expand Down
31 changes: 24 additions & 7 deletions docs/developer-tools/atlas.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,14 @@ description: Agent support for GoForj projects, including local guidance, skills

Atlas gives AI coding agents enough local project context to work inside a GoForj App without guessing at framework conventions.

<p class="gf-atlas-banner"><img src="https://raw.githubusercontent.com/goforj/atlas/main/docs/assets/banner.png" alt="GoForj Atlas — a map for your coding agent" data-no-lightbox="true"></p>

It is optional, but first-class. During `forj new`, the `Atlas - Agent Support` step can install agent guidance for the tools you use. Atlas can also be added later from an existing project.

If you are building a GoForj Project, use this guide and the `forj atlas:*` commands. That CLI path installs the right project files and runs Atlas without requiring a separate Atlas binary. Use the [standalone Atlas library reference](/atlas) only when embedding Atlas in another Go program or contributing to its Go module.

Atlas starts with read-only project inspection. GoForj make commands remain the write path for framework scaffolding, so agent context does not become an arbitrary shell or file-mutation API.

## Install and Verify

During project creation, run the wizard:
Expand All @@ -35,7 +41,7 @@ forj atlas:install --dry-run
forj atlas:update --dry-run
```

## What Atlas adds
## What Atlas Adds

Atlas installs lightweight project files that teach agents the GoForj way to build:

Expand All @@ -47,7 +53,14 @@ Atlas installs lightweight project files that teach agents the GoForj way to bui

The goal is not to make agents louder. The goal is to make them less surprising.

## Project-owned skills
| Layer | What It Gives the Agent |
| --- | --- |
| Guidance | Project-wide GoForj conventions in the agent's native instruction file. |
| Skills | Focused workflows selected for the Project's components, Apps, and starter kit. |
| MCP context | Read-only project layout, ownership, docs, routes, resources, runtime evidence, and validation plans. |
| Project skills | Team-specific rules under `.ai/skills` that Atlas synchronizes into each selected agent's native format. |

## Project-Owned Skills

Atlas ships with built-in GoForj skills, but your project can add its own.

Expand Down Expand Up @@ -113,7 +126,7 @@ Choose individual agents and surfaces when you want a smaller or more explicit i
forj atlas:install --agent codex --agent copilot --agent gemini --guidelines --skills --mcp
```

## Supported agents
## Supported Agents

Atlas is designed around local project files and editor-readable instructions, so the same project can support multiple agents:

Expand All @@ -133,7 +146,7 @@ Atlas writes each agent's native project files:
| GitHub Copilot | `.github/copilot-instructions.md` | `.github/instructions/*.instructions.md` | `.vscode/mcp.json` |
| Gemini CLI | `GEMINI.md` | `.gemini/skills/*/GEMINI.md` | `.gemini/settings.json` |

## MCP context
## MCP Context

Atlas can expose GoForj context through an MCP server. The MCP server loads docs and project metadata locally, then serves focused slices of context to the agent.

Expand Down Expand Up @@ -163,7 +176,7 @@ Atlas reports the active docs version and revision through `application-info` an

Use `version-alignment` when an agent needs to compare the project GoForj version, Atlas version, and active docs bundle before following docs from a branch or release.

## Workflow skills
## Workflow Skills

Atlas installs workflow skills for high-leverage GoForj changes. They are short, task-focused guides that tell an agent which app owns the change, which `forj make:*` command to prefer, which generated files not to edit by hand, which docs sections to read, and which validation commands prove the work.

Expand All @@ -182,7 +195,9 @@ Built-in workflow skills cover:

Atlas also includes starter-kit overlays for Vue, React, and templ/htmx projects. When a frontend task touches pages, screens, dashboards, login, auth, or UI behavior, the workflow plan can point the agent at the matching starter-kit skill so edits stay in the owning app's frontend tree.

## Agent workflow examples
Skills are entry points into verified workflows, not a second copy of the documentation. They direct the agent to bounded docs sections, current project evidence, file-ownership policy, and validation appropriate to the task.

## Agent Workflow Examples

Agents should use Atlas tools together instead of reading the whole docs site or guessing from filenames.

Expand Down Expand Up @@ -238,13 +253,15 @@ browser-logs app="app" limit=50

That gives the agent app/runtime identity, local URLs, recent logs, metrics labels, browser errors, and known operator resources before code changes begin.

The runtime evidence tools do not require the Lighthouse browser UI to be open. When Lighthouse-backed local evidence exists, Atlas can include its links or records; when logs, browser entries, routes, URLs, or metrics targets are absent, it reports the missing evidence instead of inventing it.

For human-readable versions of common evidence loops, see [Atlas Debug Recipes](/developer-tools/atlas-debug-recipes).

`runtime-snapshot` and `debug-plan` are evidence tools. They report missing logs, URLs, routes, browser entries, metrics targets, or resource links instead of inventing values.

`generated-file-policy` reports classification, preferred action, and ownership for generated files, app-owned files, app-specific files, migrations, frontend files, config, docs, and unknown paths. Projects can override ownership rules in `.goforj/atlas.json`.

## Daily use
## Daily Use

Most users do not need to run Atlas commands every day. Once installed, your agent reads the local guidance files and, when configured, asks the MCP server for focused docs context.

Expand Down
15 changes: 14 additions & 1 deletion docs/developer-tools/forj-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ flowchart LR

In a GoForj Project, this can bring up local dependencies, prepare the database, build the frontend, compile the App, start its Runtime, and watch the files that feed each step. You do not need to keep separate build, frontend, and server commands synchronized in different terminals.

::: warning Development only
`forj dev` is a local development supervisor. Do not run it as a production process manager or deploy its watcher lifecycle. Production supervisors should execute the built artifact, such as `./bin/app`, `./bin/app api`, `./bin/app worker`, or `./bin/app scheduler`.
:::

When a file changes, `forj dev` reruns only the affected work. It replaces the running App after a successful build. If the build fails, the last working Runtime stays up while the error remains visible in the transcript. Fix the error, save again, and the loop continues.

## Start the Development Loop
Expand Down Expand Up @@ -237,7 +241,16 @@ dev:
down_on_exit: true
```

Startup first runs configured App bootstrap builds so pre-tasks can call built App commands. It then runs `dev.pre`, performs configured database setup and auto-migration, and runs any generated tasks deliberately ordered after migration. Finally, it builds App-owned SPAs, rebuilds their Apps, and starts persistent watcher and runtime processes.
For a modern `dev.apps` configuration whose setup tasks match GoForj's generated conventions, startup follows this order:

1. Run conventional setup tasks that do not require an App binary, such as starting Docker Compose and waiting for its database.
2. Build each App-owned SPA.
3. Build the participating Apps once.
4. Prepare configured development databases and run auto-migration.
5. Run generated tasks that must follow migration, then rebuild when those tasks changed generated source.
6. Start the persistent watchers and App runtime processes.

This ordering gives the migration step a current App binary while ensuring that embedded frontend assets are already present in that binary. If custom or legacy `dev.pre` tasks do not match the generated setup phases, GoForj preserves their historical ordering and may perform an earlier bootstrap build plus a post-setup rebuild. Keep binary-dependent custom setup explicit instead of relying on a generated task name to change its phase.

For npm-backed starter kits, new Projects generate this dependency setup task:

Expand Down
40 changes: 39 additions & 1 deletion docs/developer-tools/wiring-recipes.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ For an additional app, replace `app/...` with the owning app's `app/<name>/...`.

Use the most specific generated set that owns the surface. If a generated file is not present, the app probably does not have that component enabled.

The `inject_*_app.go` files are App-owned extension points. GoForj creates them with the App and make commands can update them, but normal Project regeneration preserves your edits. In contrast, `app/wire/app.go` and `app/wire/wire_gen.go` are framework-generated output and should not be edited.

## Generated Resources

When a resource has a make command, use it before editing provider sets by hand. The command creates the resource and updates the active App's generated wiring boundaries.
Expand Down Expand Up @@ -75,6 +77,23 @@ The [controller verification workflow](/applications/controllers#verify-the-resu

The controller can depend on an application service already provided by the app service set. If Wire cannot provide that service, add the service constructor to `app/wire/inject_services_app.go`.

The controller injector uses its own set rather than `appSet`:

<!-- go-example: illustrative-fragment -->
```go
package wire

import (
"github.com/goforj/wire"

"myapp/internal/users"
)

var appHttpControllerSet = wire.NewSet(
users.NewController,
)
```

Verify the result:

```bash
Expand All @@ -88,7 +107,25 @@ The [command creation workflow](/applications/commands#create-a-command) shows t

Command constructors should receive application services as parameters. They should not create repositories, managers, clients, or services themselves.

Commands also need to be exposed through the generated command collection. See [Commands](/applications/commands) for the command-specific registration path.
Commands need both an App-owned provider and an App-owned CLI field. `forj make:command` updates both locations. For a manually written command, edit both files:

<!-- go-example: illustrative-fragment -->
```go
// app/wire/inject_cmd_app.go
var appCommandSet = wire.NewSet(
reports.NewReconcileCmd,
)
```

<!-- go-example: illustrative-fragment -->
```go
// app/commands.go
type Commands struct {
ReconcileCmd reports.ReconcileCmd `cmd:""`
}
```

The existing `NewCommands` constructor in `app/commands.go` must also accept the injected pointer and copy it into the collection. See [Commands](/applications/commands) for the complete registration path.

## Named Resource

Expand Down Expand Up @@ -132,6 +169,7 @@ forj build

::: warning Common mistakes
- Do not add constructors to `app/wire/wire_gen.go`; it is generated output.
- Do not edit `app/wire/app.go`; it is Framework-managed App assembly.
- Do not register a controller in the service set when it belongs in the HTTP controller set.
- Do not create dependencies inside commands or controllers when they should be constructor parameters.
- Do not use package globals to avoid wiring a provider.
Expand Down
Loading