Config: .env = projects root only; derive each project's forge from git
Reduce .env to just REPOS_HOST_PATH (the projects root); runtime bootstrap moves to compose/defaults. Projects are the subdirectories of the single root — removed the project-directories feature (store table, service methods, /api/config/project-dirs, Settings section). Each project's forge is derived from its git remote (matched to a configured forge, else the bare host) and shown as a pill next to its name (State.Forge via scanner ForgeFor + svc.ForgeDisplay). Removed first-run .env seeding; forges + identity are managed in Settings. Added forge.HostOf. AGENT.md updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -15,7 +15,6 @@ class SettingsPanel extends HTMLElement {
|
||||
connectedCallback() {
|
||||
this.#renderShell();
|
||||
this.#loadForges();
|
||||
this.#loadDirs();
|
||||
this.#loadIdentity();
|
||||
}
|
||||
|
||||
@@ -67,51 +66,6 @@ class SettingsPanel extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
// --- project directories --------------------------------------------------
|
||||
|
||||
async #loadDirs() {
|
||||
const box = this.shadowRoot.getElementById('dirs');
|
||||
try {
|
||||
const dirs = await this.#json('GET', '/api/config/project-dirs');
|
||||
if (!dirs || dirs.length === 0) {
|
||||
box.innerHTML = `<p class="muted">No project directories configured.</p>`;
|
||||
return;
|
||||
}
|
||||
box.replaceChildren(...dirs.map((d) => {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'row';
|
||||
row.innerHTML = `
|
||||
<label class="dir">
|
||||
<input type="checkbox" ${d.enabled ? 'checked' : ''}>
|
||||
<code>${this.#esc(d.path)}</code>
|
||||
</label>`;
|
||||
row.querySelector('input').onchange = (e) =>
|
||||
this.#put(`/api/config/project-dirs/${d.id}`, { enabled: e.target.checked }, e.target.checked ? 'Directory enabled' : 'Directory disabled');
|
||||
const del = document.createElement('button');
|
||||
del.className = 'danger';
|
||||
del.textContent = 'Remove';
|
||||
del.onclick = () => this.#delete(`/api/config/project-dirs/${d.id}`, 'Directory removed', () => this.#loadDirs());
|
||||
row.appendChild(del);
|
||||
return row;
|
||||
}));
|
||||
} catch (err) {
|
||||
box.innerHTML = `<p class="error">${this.#esc(err.message)}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
async #addDir() {
|
||||
const path = this.shadowRoot.getElementById('d-path').value.trim();
|
||||
if (!path) { this.#toast('A path is required', 'error'); return; }
|
||||
try {
|
||||
await this.#json('POST', '/api/config/project-dirs', { path });
|
||||
this.shadowRoot.getElementById('d-path').value = '';
|
||||
this.#toast('Directory added', 'success');
|
||||
this.#loadDirs();
|
||||
} catch (err) {
|
||||
this.#toast(`Add directory failed: ${err.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// --- git identity ---------------------------------------------------------
|
||||
|
||||
async #loadIdentity() {
|
||||
@@ -174,6 +128,8 @@ class SettingsPanel extends HTMLElement {
|
||||
border-radius: var(--radius); padding: 16px 18px; margin-bottom: 16px; }
|
||||
h2 { font-size: 15px; margin: 0 0 4px; }
|
||||
.hint { color: var(--color-fg-muted); font-size: 13px; margin: 0 0 12px; }
|
||||
.intro { color: var(--color-fg-muted); font-size: 13px; margin: 0 0 16px; }
|
||||
.intro code { background: var(--surface-2); padding: 1px 6px; border-radius: var(--radius-sm); }
|
||||
.row { display: flex; align-items: center; gap: 10px; padding: 6px 0;
|
||||
border-top: 1px solid var(--border); }
|
||||
.row:first-of-type { border-top: none; }
|
||||
@@ -197,6 +153,10 @@ class SettingsPanel extends HTMLElement {
|
||||
.error { color: var(--color-danger); }
|
||||
</style>
|
||||
|
||||
<p class="intro">Projects are the subdirectories of the root folder set in
|
||||
<code>.env</code>; each project's forge is detected from its git remote and
|
||||
shown next to its name on the dashboard.</p>
|
||||
|
||||
<section>
|
||||
<h2>Forges</h2>
|
||||
<p class="hint">Hosting servers (Gitea/Forgejo) and their access tokens.
|
||||
@@ -210,17 +170,6 @@ class SettingsPanel extends HTMLElement {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Project directories</h2>
|
||||
<p class="hint">Directories to scan for repositories. Paths are inside the
|
||||
container — they must be under a mounted directory (e.g. under <code>/repos</code>).</p>
|
||||
<div id="dirs"><p class="muted">Loading…</p></div>
|
||||
<div class="form">
|
||||
<input id="d-path" type="text" class="grow" placeholder="/repos/some-folder">
|
||||
<button id="d-add">Add directory</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Git identity</h2>
|
||||
<p class="hint">Author used for commits the app makes.</p>
|
||||
@@ -233,7 +182,6 @@ class SettingsPanel extends HTMLElement {
|
||||
`;
|
||||
|
||||
this.shadowRoot.getElementById('f-add').onclick = () => this.#addForge();
|
||||
this.shadowRoot.getElementById('d-add').onclick = () => this.#addDir();
|
||||
this.shadowRoot.getElementById('i-save').onclick = () => this.#saveIdentity();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,30 +1,28 @@
|
||||
# settings-panel
|
||||
|
||||
## Intent
|
||||
The UI for the config store (AGENT.md §1.3) — manage forge hosts + tokens,
|
||||
project directories to scan, and the git commit identity, replacing hand-editing
|
||||
`.env` for domain config. Served at `/settings`.
|
||||
The UI for the config store (AGENT.md §1.5) — manage forge hosts + tokens and the
|
||||
git commit identity, replacing hand-editing `.env` for domain config. Projects
|
||||
themselves are the subdirectories of the single root set in `.env`, and each
|
||||
project's forge is derived from its git remote (not configured here). Served at
|
||||
`/settings`.
|
||||
|
||||
## Public surface
|
||||
- **Tag:** `<settings-panel>`
|
||||
- **Fetches:** `GET /api/config/forges`, `GET /api/config/project-dirs`,
|
||||
`GET /api/config/identity`.
|
||||
- **Fetches:** `GET /api/config/forges`, `GET /api/config/identity`.
|
||||
- **Writes:**
|
||||
- Forges: `POST /api/config/forges` (add), `DELETE /api/config/forges/:id`.
|
||||
- Project dirs: `POST /api/config/project-dirs` (add),
|
||||
`PUT /api/config/project-dirs/:id` (enable/disable),
|
||||
`DELETE /api/config/project-dirs/:id`.
|
||||
- Identity: `PUT /api/config/identity`.
|
||||
- **Reports:** results via `toast` CustomEvents (needs `<toast-host>` on the page).
|
||||
|
||||
## History
|
||||
- 2026-09-22: created — settings UI for the SQLite config store (forges, project
|
||||
dirs, git identity).
|
||||
- 2026-09-22: removed the Project directories section — projects are now the
|
||||
subdirectories of the single `.env` root; only Forges + Git identity remain.
|
||||
|
||||
## Notes / gotchas
|
||||
- Tokens are **write-only** from the client: the server returns only `hasToken`,
|
||||
never the value. Removing + re-adding a forge is how you rotate a token.
|
||||
- Project-dir paths are **container paths** and must resolve inside the container
|
||||
(under a mounted base); the server validates existence before adding.
|
||||
- Adding/removing a forge reapplies git auth (the server sets an `http.extraheader`
|
||||
per forge); changes take effect immediately.
|
||||
|
||||
Reference in New Issue
Block a user