---
id: w4/m6.5/t003
title: "Env-var table: keys list + per-key 'Show secret' value reveal (Render-style)"
worker: worker4
status: done
estimate: 45m
depends_on: [w4/m6.5/t001, w4/m6.5/t002]
---

## Objective

Render the read side of the Environment tab, Render-style: a table of the service's env-var **keys** with masked values, each row a "Show secret" control that fetches and reveals that variable's value on demand (`envVar(key)`) — matching Render's keys-first / reveal-on-demand pattern (never bulk-fetching values). Handle loading / empty / error states.

## Context

- Depends on t001's hooks (`use-env-vars` for the keys list, `use-env-var-value` for reveal) and t002's route.
- Render's env page (reference): a `Key | Value` table, values shown as `••••••` with a per-row "Show secret" toggle; keys come from `envVarKeys`, a value is fetched only when revealed.
- Error surfaces to wire: 403 (tuple-less key) → an inline "not authorized" state; 503 (store unconfigured, `BEX_OPENBAO_URL` unset) → an "environment variables unavailable" state — neither should crash the page.
- Use the dashboard's shadcn table + existing loading/skeleton and empty-state patterns from the services/overview pages.

## Steps

1. Build an `EnvVarsTable` component: rows from `envVarKeys`, masked value + "Show secret" that lazily loads `envVar(key)`.
2. Wire loading / empty ("No environment variables") / 403 / 503 states.
3. Mount it on the Environment route.

## Files

- `dashboard/src/features/services/components/env-vars-table.tsx` (new)
- `dashboard/src/routes/services.$serviceId.env.tsx`

## Acceptance criteria

- [ ] Keys render with masked values; "Show secret" reveals a single value via `envVar(key)`, not the bulk list.
- [ ] Empty, 403, and 503 states render without crashing.

## Out of scope

- Add / update / delete (t004).
