feat: custom display name + config file foundation (#399)

Adds user-editable display names and persistent config via ~/.plannotator/config.json.

- ConfigStore singleton with precedence: server config file > cookie > default
- Editable identity input in Settings with "Use git name" and regenerate buttons
- POST /api/config endpoint for write-back across all 6 servers
- getServerConfig() reads config fresh per request (no stale cache)
- Eager constructor hydration so the store is safe to read before init()
- vendor.sh as single source of truth for Pi extension vendoring
- Vendor parity test to prevent missing generated modules

Closes #396
This commit is contained in:
Michael Ramos
2026-03-26 11:54:22 -07:00
committed by GitHub
parent e53e76cc3e
commit 401793e35f
25 changed files with 589 additions and 85 deletions
+16
View File
@@ -15,6 +15,7 @@ import { getRepoInfo } from "./repo";
import { handleImage, handleUpload, handleAgents, handleServerReady, handleDraftSave, handleDraftLoad, handleDraftDelete, handleFavicon, type OpencodeClient } from "./shared-handlers";
import { contentHash, deleteDraft } from "./draft";
import { createEditorAnnotationHandler } from "./editor-annotations";
import { saveConfig, detectGitUser, getServerConfig } from "./config";
import { type PRMetadata, type PRReviewFileComment, fetchPRFileContent, fetchPRContext, submitPRReview, fetchPRViewedFiles, markPRFilesViewed, getPRUser, prRefFromMetadata, getDisplayRepo, getMRLabel, getMRNumberLabel } from "./pr";
import { createAIEndpoints, ProviderRegistry, SessionManager, createProvider, type AIEndpoints, type PiSDKConfig } from "@plannotator/ai";
import { isWSL } from "./browser";
@@ -191,6 +192,7 @@ export async function startReviewServer(
const isRemote = isRemoteSession();
const configuredPort = getServerPort();
const wslFlag = await isWSL();
const gitUser = detectGitUser();
// Detect repo info (cached for this session)
// In PR mode, derive from metadata instead of local git
@@ -261,6 +263,7 @@ export async function startReviewServer(
...(isPRMode && { prMetadata, platformUser }),
...(isPRMode && initialViewedFiles.length > 0 && { viewedFiles: initialViewedFiles }),
...(currentError && { error: currentError }),
serverConfig: getServerConfig(gitUser),
});
}
@@ -400,6 +403,19 @@ export async function startReviewServer(
}
}
// API: Update user config (write-back to ~/.plannotator/config.json)
if (url.pathname === "/api/config" && req.method === "POST") {
try {
const body = (await req.json()) as { displayName?: string };
if (body.displayName !== undefined) {
saveConfig({ displayName: body.displayName });
}
return Response.json({ ok: true });
} catch {
return Response.json({ error: "Invalid request" }, { status: 400 });
}
}
// API: Serve images (local paths or temp uploads)
if (url.pathname === "/api/image") {
return handleImage(req);