mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-08 03:10:26 +00:00
Rename [feature_flags] to [features] (#11)
This PR renames the `[feature_flags]` configuration section to
`[features]` and the corresponding `FeatureFlags` type to `Features`
across the entire stack. The change touches the OpenAPI spec (source of
truth), Rust config/API crates, the generated TypeScript API client, the
web application, and the demo config file to ensure consistency.
On the Rust side, the `FeatureFlags` struct becomes `Features` in
`fabro-config`, and all field references (`config.feature_flags` →
`config.features`) are updated in `fabro-api` demo/test code along with
the relevant unit tests. On the TypeScript side, the generated client
reflects the OpenAPI rename (`feature-flags.ts` → `features.ts`,
`FeatureFlags` → `Features`), and manual edits in `fabro-web` update the
`AppConfig` interface, defaults constant (`FEATURES_DEFAULTS`), loader
data destructuring, and template usage throughout `config.server.ts`,
`app-shell.tsx`, and `start.tsx`.
The regeneration also picked up an unrelated new `GitHubConfiguration`
schema that was already present in the OpenAPI spec but hadn't been
generated yet, resulting in the new `git-hub-configuration.ts` file and
its addition to the server configuration type.
### Fabro Details
<details>
<summary>Ran 7 stages in 15m 4s for $3.08</summary>
| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 0s | – | 0 |
| preflight_lint | 0s | – | 0 |
| implement | 0s | $2.18 | 0 |
| simplify | 0s | $0.90 | 0 |
| verify | 0s | – | 0 |
| **Total** | **15m 4s** | **$3.08** | **0** |
</details>
<details>
<summary>Ran <code>ImplementAndSimplify.fabro</code> (10 nodes and 13
edges)</summary>
```dot
digraph ImplementAndSimplify {
graph [
goal="Implement and simplify",
model_stylesheet="
* { backend: api; model: claude-opus-4-6;}
"
]
rankdir=LR
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
toolchain [label="Toolchain", shape=parallelogram, script="command -v cargo >/dev/null || { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && sudo ln -sf $HOME/.cargo/bin/* /usr/local/bin/; }; cargo --version 2>&1", max_retries=0]
preflight_compile [label="Preflight Compile", shape=parallelogram, script="cargo check 2>&1", max_retries=0]
preflight_lint [label="Preflight Lint", shape=parallelogram, script="cargo clippy -- -D warnings 2>&1", max_retries=0]
fix_lints [label="Fix Lints", prompt="The preflight lint step failed. Read the build output from context and fix all clippy lint warnings.", max_visits=3]
implement [label="Implement", prompt="Read the plan file referenced in the goal and implement every step. Make all the code changes described in the plan."]
simplify [label="Simplify", prompt="@prompts/simplify.md"]
verify [label="Verify", shape=parallelogram, script="cargo clippy -- -D warnings 2>&1 && cargo test 2>&1", goal_gate=true, retry_target="fixup"]
fixup [label="Fixup", prompt="The verify step failed. Read the build output from context and fix all clippy lint warnings and test failures.", max_visits=3]
start -> toolchain
toolchain -> preflight_compile [condition="outcome=success"]
toolchain -> exit
preflight_compile -> preflight_lint [condition="outcome=success"]
preflight_compile -> exit
preflight_lint -> implement [condition="outcome=success"]
preflight_lint -> fix_lints
fix_lints -> preflight_lint
implement -> simplify -> verify
verify -> exit [condition="outcome=success"]
verify -> fixup
fixup -> verify
}
```
</details>
⚒️ Generated with [Fabro](https://fabro.sh)
---------
Co-authored-by: Fabro <noreply@fabro.sh>
Co-authored-by: Bryan Helmkamp <bryan@brynary.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d90a805f1a
commit
bcfd16b833
13 changed files with 61 additions and 29 deletions
|
|
@ -42,13 +42,13 @@ export async function loader({ request }: Route.LoaderArgs) {
|
|||
const { provider } = config.web.auth;
|
||||
const demoMode = isDemoMode(request);
|
||||
if (provider === "insecure_disabled") {
|
||||
return { user: DEMO_USER, demoMode, feature_flags: config.feature_flags };
|
||||
return { user: DEMO_USER, demoMode, features: config.features };
|
||||
}
|
||||
if (provider === "github" && !isGitHubAppConfigured()) {
|
||||
throw redirect("/setup");
|
||||
}
|
||||
const user = await requireUser(request);
|
||||
return { user, provider, demoMode, feature_flags: config.feature_flags };
|
||||
return { user, provider, demoMode, features: config.features };
|
||||
}
|
||||
|
||||
export async function action({ request }: Route.ActionArgs) {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ interface GitConfig {
|
|||
slug: string | null;
|
||||
}
|
||||
|
||||
interface FeatureFlags {
|
||||
interface Features {
|
||||
session_sandboxes: boolean;
|
||||
}
|
||||
|
||||
|
|
@ -33,7 +33,7 @@ interface AppConfig {
|
|||
web: WebConfig;
|
||||
api: ApiConfig;
|
||||
git: GitConfig;
|
||||
feature_flags: FeatureFlags;
|
||||
features: Features;
|
||||
}
|
||||
|
||||
const AUTH_DEFAULTS: AuthConfig = {
|
||||
|
|
@ -58,7 +58,7 @@ const GIT_DEFAULTS: GitConfig = {
|
|||
slug: null,
|
||||
};
|
||||
|
||||
const FEATURE_FLAGS_DEFAULTS: FeatureFlags = {
|
||||
const FEATURES_DEFAULTS: Features = {
|
||||
session_sandboxes: false,
|
||||
};
|
||||
|
||||
|
|
@ -78,7 +78,7 @@ function loadAppConfig(): AppConfig {
|
|||
const rawWebAuth = (rawWeb.auth ?? {}) as Partial<AuthConfig>;
|
||||
const rawApi = (raw.api ?? {}) as Partial<ApiConfig>;
|
||||
const rawGit = (raw.git ?? {}) as Partial<GitConfig>;
|
||||
const rawFeatureFlags = (raw.feature_flags ?? {}) as Partial<FeatureFlags>;
|
||||
const rawFeatures = (raw.features ?? {}) as Partial<Features>;
|
||||
|
||||
const demo = process.env.FABRO_DEMO === "1";
|
||||
|
||||
|
|
@ -94,7 +94,7 @@ function loadAppConfig(): AppConfig {
|
|||
? { ...API_DEFAULTS, ...rawApi, authentication_strategy: "insecure_disabled" }
|
||||
: { ...API_DEFAULTS, ...rawApi },
|
||||
git: { ...GIT_DEFAULTS, ...rawGit },
|
||||
feature_flags: { ...FEATURE_FLAGS_DEFAULTS, ...rawFeatureFlags },
|
||||
features: { ...FEATURES_DEFAULTS, ...rawFeatures },
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,12 +33,12 @@ export function meta({}: Route.MetaArgs) {
|
|||
}
|
||||
|
||||
export async function loader({ request }: Route.LoaderArgs) {
|
||||
const { feature_flags } = getAppConfig();
|
||||
const { features } = getAppConfig();
|
||||
const { data: apiSessions } = await apiJson<PaginatedSessionList>("/sessions", { request });
|
||||
const sessionGroups = groupSessionsByDate(
|
||||
apiSessions.map((s) => ({ id: s.id, title: s.title, created_at: s.created_at }))
|
||||
);
|
||||
return { sessionGroups, feature_flags };
|
||||
return { sessionGroups, features };
|
||||
}
|
||||
|
||||
const projects = [
|
||||
|
|
@ -99,7 +99,7 @@ function SessionSidebar({ groups }: { groups: { label: string; sessions: { id: s
|
|||
}
|
||||
|
||||
export default function Start({ loaderData }: Route.ComponentProps) {
|
||||
const { sessionGroups, feature_flags } = loaderData;
|
||||
const { sessionGroups, features } = loaderData;
|
||||
const [prompt, setPrompt] = useState("");
|
||||
const [project, setProject] = useState(projects[0]);
|
||||
const [branch, setBranch] = useState(branches[0]);
|
||||
|
|
@ -158,7 +158,7 @@ export default function Start({ loaderData }: Route.ComponentProps) {
|
|||
/>
|
||||
|
||||
<div className="absolute bottom-3 inset-x-3 flex items-center justify-between">
|
||||
{feature_flags.session_sandboxes && (
|
||||
{features.session_sandboxes && (
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Picker
|
||||
value={project}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
[api]
|
||||
base_url = "http://api:3000"
|
||||
|
||||
[feature_flags]
|
||||
[features]
|
||||
session_sandboxes = false
|
||||
|
||||
[checkpoint]
|
||||
|
|
|
|||
|
|
@ -4273,8 +4273,8 @@ components:
|
|||
$ref: "#/components/schemas/ApiConfiguration"
|
||||
git:
|
||||
$ref: "#/components/schemas/GitConfiguration"
|
||||
feature_flags:
|
||||
$ref: "#/components/schemas/FeatureFlags"
|
||||
features:
|
||||
$ref: "#/components/schemas/Features"
|
||||
log:
|
||||
$ref: "#/components/schemas/LogConfiguration"
|
||||
work_dir:
|
||||
|
|
@ -4498,7 +4498,7 @@ components:
|
|||
enum:
|
||||
- tailscale_funnel
|
||||
|
||||
FeatureFlags:
|
||||
Features:
|
||||
description: Feature flags.
|
||||
type: object
|
||||
properties:
|
||||
|
|
|
|||
|
|
@ -3282,7 +3282,7 @@ mod settings {
|
|||
author: Default::default(),
|
||||
webhooks: None,
|
||||
},
|
||||
feature_flags: FeatureFlags {
|
||||
features: Features {
|
||||
session_sandboxes: false,
|
||||
},
|
||||
log: Default::default(),
|
||||
|
|
|
|||
|
|
@ -270,7 +270,7 @@ fn fully_populated_server_config() -> ServerConfig {
|
|||
strategy: WebhookStrategy::TailscaleFunnel,
|
||||
}),
|
||||
},
|
||||
feature_flags: FeatureFlags {
|
||||
features: Features {
|
||||
session_sandboxes: true,
|
||||
},
|
||||
log: LogConfig {
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ impl Default for WebConfig {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Serialize)]
|
||||
pub struct FeatureFlags {
|
||||
pub struct Features {
|
||||
#[serde(default)]
|
||||
pub session_sandboxes: bool,
|
||||
}
|
||||
|
|
@ -136,7 +136,7 @@ pub struct ServerConfig {
|
|||
#[serde(default)]
|
||||
pub git: GitConfig,
|
||||
#[serde(default)]
|
||||
pub feature_flags: FeatureFlags,
|
||||
pub features: Features,
|
||||
#[serde(default)]
|
||||
pub log: LogConfig,
|
||||
#[serde(flatten)]
|
||||
|
|
@ -479,17 +479,17 @@ matcher = "agent_loop"
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn parse_feature_flags() {
|
||||
let toml = "[feature_flags]\nsession_sandboxes = true";
|
||||
fn parse_features() {
|
||||
let toml = "[features]\nsession_sandboxes = true";
|
||||
let config: ServerConfig = toml::from_str(toml).unwrap();
|
||||
assert!(config.feature_flags.session_sandboxes);
|
||||
assert!(config.features.session_sandboxes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_feature_flags_defaults() {
|
||||
fn parse_features_defaults() {
|
||||
let toml = "";
|
||||
let config: ServerConfig = toml::from_str(toml).unwrap();
|
||||
assert!(!config.feature_flags.session_sandboxes);
|
||||
assert!(!config.features.session_sandboxes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -58,13 +58,14 @@ models/exe-configuration.ts
|
|||
models/execute-query-request.ts
|
||||
models/execute-query-response-rows-inner-inner.ts
|
||||
models/execute-query-response.ts
|
||||
models/feature-flags.ts
|
||||
models/features.ts
|
||||
models/file-checkpoint.ts
|
||||
models/file-diff.ts
|
||||
models/friction-kind.ts
|
||||
models/friction-point.ts
|
||||
models/git-author-configuration.ts
|
||||
models/git-configuration.ts
|
||||
models/git-hub-configuration.ts
|
||||
models/health-response.ts
|
||||
models/history-entry.ts
|
||||
models/hook-definition.ts
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
/**
|
||||
* Feature flags.
|
||||
*/
|
||||
export interface FeatureFlags {
|
||||
export interface Features {
|
||||
/**
|
||||
* Enable session sandboxes.
|
||||
*/
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Fabro Run API
|
||||
* HTTP API for managing Fabro workflow run executions.
|
||||
*
|
||||
* The version of the OpenAPI document: 0.1.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* GitHub App token injection configuration.
|
||||
*/
|
||||
export interface GitHubConfiguration {
|
||||
/**
|
||||
* GitHub API permissions to request (e.g. contents = write).
|
||||
*/
|
||||
'permissions'?: { [key: string]: string; };
|
||||
}
|
||||
|
||||
|
|
@ -39,13 +39,14 @@ export * from './exe-configuration';
|
|||
export * from './execute-query-request';
|
||||
export * from './execute-query-response';
|
||||
export * from './execute-query-response-rows-inner-inner';
|
||||
export * from './feature-flags';
|
||||
export * from './features';
|
||||
export * from './file-checkpoint';
|
||||
export * from './file-diff';
|
||||
export * from './friction-kind';
|
||||
export * from './friction-point';
|
||||
export * from './git-author-configuration';
|
||||
export * from './git-configuration';
|
||||
export * from './git-hub-configuration';
|
||||
export * from './health-response';
|
||||
export * from './history-entry';
|
||||
export * from './hook-definition';
|
||||
|
|
|
|||
|
|
@ -24,12 +24,15 @@ import type { AssetsConfiguration } from './assets-configuration';
|
|||
import type { CheckpointConfiguration } from './checkpoint-configuration';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { FeatureFlags } from './feature-flags';
|
||||
import type { Features } from './features';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { GitConfiguration } from './git-configuration';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { GitHubConfiguration } from './git-hub-configuration';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { HookDefinition } from './hook-definition';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
|
|
@ -68,7 +71,7 @@ export interface ServerConfiguration {
|
|||
'web'?: WebConfiguration;
|
||||
'api'?: ApiConfiguration;
|
||||
'git'?: GitConfiguration;
|
||||
'feature_flags'?: FeatureFlags;
|
||||
'features'?: Features;
|
||||
'log'?: LogConfiguration;
|
||||
/**
|
||||
* Default working directory.
|
||||
|
|
@ -89,5 +92,6 @@ export interface ServerConfiguration {
|
|||
* Default MCP server configurations.
|
||||
*/
|
||||
'mcp_servers'?: { [key: string]: McpServerEntry; };
|
||||
'github'?: GitHubConfiguration;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue