Files
Triple-C/app/src/components/projects/EnvVarsEditor.tsx
T
shadow-testandClaude Opus 5 9b2f4fe79f
Build App (Preview) / compute-version (pull_request) Successful in 7s
Build App (Preview) / create-release (pull_request) Successful in 3s
Build App (Preview) / build-macos (pull_request) Successful in 2m56s
Build App (Preview) / build-windows (pull_request) Successful in 5m33s
Build App (Preview) / build-linux (pull_request) Successful in 6m47s
Build App (Preview) / prune-previews (pull_request) Successful in 4s
Give the env var its value box back, and stop labelling the secret
Two separate faults, both reachable from one screenshot of the Global
Environment Variables editor.

The value input was collapsed to a sliver, so a variable looked like it
had lost its value. `inputClass` carries `w-full`, and the `w-2/5` on the
key input did not beat it — class-attribute order is not what resolves
that conflict, stylesheet order is. The key therefore asked for the whole
row, and the value input, whose `flex-1` gives it a basis of 0 and only
the leftover space, got almost nothing. Widths now live on wrapper divs,
where nothing competes with them.

The fingerprint that detects custom-env changes was a plaintext
`KEY=VALUE` join, and it is written as the `triple-c.custom-env-fingerprint`
label. Labels are readable by anything on the host via `docker inspect`,
`docker commit` copies them onto the project's snapshot image, and the
recreation check logs both sides on a mismatch — so an API token set as a
custom variable was published to all three. It is hashed now, exactly as
`triple-c.git-token-hash` already was. Empty stays empty, so "nothing
configured" still reads as an empty label.

Changing the fingerprint format means every project's label mismatches
once: expect a single container recreation per project on next start.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 15:27:19 -07:00

104 lines
3.2 KiB
TypeScript

import { useEffect, useState } from "react";
import type { EnvVar } from "../../lib/types";
import Button from "../ui/Button";
import { monoInputClass } from "../ui/Field";
interface Props {
envVars: EnvVar[];
disabled: boolean;
disabledReason?: string;
onSave: (vars: EnvVar[]) => Promise<unknown>;
}
/** Env-var table. Used inline in Project Home → Config and in global Settings. */
export default function EnvVarsEditor({
envVars: initial,
disabled,
disabledReason,
onSave,
}: Props) {
const [vars, setVars] = useState<EnvVar[]>(initial);
useEffect(() => {
setVars(initial);
}, [initial]);
const updateVar = (index: number, field: keyof EnvVar, value: string) => {
const updated = [...vars];
updated[index] = { ...updated[index], [field]: value };
setVars(updated);
};
return (
<div className="space-y-2">
{disabled && disabledReason && (
<p className="px-2 py-1.5 bg-[var(--warning-muted)] border border-[var(--warning)]/30 rounded-[var(--radius-control)] text-xs text-[var(--warning)]">
{disabledReason}
</p>
)}
{vars.length === 0 && (
<p className="text-xs text-[var(--text-secondary)]">
No environment variables configured.
</p>
)}
{/* The row's widths live on wrapper divs, not on the inputs. `inputClass`
carries `w-full`, and a width utility on the input itself does not beat
it — class-attribute order is not what resolves the conflict, stylesheet
order is. Sizing the key input directly left it asking for the whole row
and collapsed the value input, whose `flex-1` basis of 0 gave it only the
leftover space, to an unusable sliver. */}
{vars.map((ev, i) => (
<div key={i} className="flex gap-2 items-center">
<div className="w-2/5 shrink-0">
<input
value={ev.key}
onChange={(e) => updateVar(i, "key", e.target.value)}
onBlur={() => onSave(vars)}
placeholder="KEY"
aria-label={`Environment variable ${i + 1} name`}
disabled={disabled}
className={monoInputClass}
/>
</div>
<div className="flex-1 min-w-0">
<input
value={ev.value}
onChange={(e) => updateVar(i, "value", e.target.value)}
onBlur={() => onSave(vars)}
placeholder="value"
aria-label={`Environment variable ${i + 1} value`}
disabled={disabled}
className={monoInputClass}
/>
</div>
<Button
variant="danger"
disabled={disabled}
aria-label={`Remove environment variable ${ev.key || i + 1}`}
onClick={() => {
const updated = vars.filter((_, j) => j !== i);
setVars(updated);
onSave(updated);
}}
>
Remove
</Button>
</div>
))}
<Button
disabled={disabled}
onClick={() => {
const updated = [...vars, { key: "", value: "" }];
setVars(updated);
onSave(updated);
}}
>
+ Add variable
</Button>
</div>
);
}