Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/31391-settings-map-keyed-rows.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@object-ui/console': patch
'@object-ui/i18n': patch
---

feat(console): Setup draws a `map` setting as a table of keyed rows instead of "Unsupported specifier type: map"

Clause-②: yes
Metadata-protocol: none

- **`@object-ui/console`:** a settings row whose specifier is `type: 'map'` (Data Lifecycle's retention overrides, row quotas and per-class quota defaults) shows one row per stored entry, in stored order, under the declared `keyColumn` and `columns` headers. A row is added by picking its key from the keys the server serves as the specifier's `options`; a key another row uses is not offered again, and a stored key the server no longer offers stays on its row marked "Not offered". A `duration` cell is a whole number plus a unit (hours, days, weeks, years) and is saved as the literal (`'90d'`); a `positive_integer` cell is saved as a number. Save sends the map as an object through the page's existing patch, and removing every row sends `null`. A refused save marks the row its `constraint.entry` names, and the cell its `constraint.column` names, beside the server's sentence. An env-pinned map is read-only. Any other unknown specifier type still draws the unsupported-type line.
- **`@object-ui/i18n`:** eleven `console.settingsView.map*` keys in every locale pack: the add picker, the remove button, the "Not offered" marker and its hint, the empty-table line, the key search placeholder, the unit select's name, and the four duration units.
23 changes: 23 additions & 0 deletions ui/apps/console/src/pages/settings/SettingsField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
import { pickLocalized, useObjectTranslation } from '@object-ui/i18n';
import { AlertTriangle, ChevronRight, Info, RotateCcw } from 'lucide-react';
import { EnvLockBadge } from './EnvLockBadge';
import { SettingsMapField, type MapRefusalAt } from './SettingsMapField';
import {
resolveLabel,
type Specifier,
Expand Down Expand Up @@ -115,6 +116,12 @@ export interface SettingsFieldProps {
* sitting under a red error reads as if the field has two states at once.
*/
error?: string;
/**
* Where the last refusal points inside a `map` value (objectstack#31391):
* the save door's `constraint.entry` and `constraint.column`. The row and
* cell named are marked; `error` stays the sentence. Read by `map` only.
*/
errorAt?: MapRefusalAt;
/**
* A fact the PAGE knows about this row that the manifest cannot carry
* (objectstack#30313: the address the server judges beside Allowed IP
Expand Down Expand Up @@ -586,6 +593,7 @@ export function SettingsField(props: SettingsFieldProps) {
saving,
labels,
error,
errorAt,
note,
writeScope,
onClearSaved,
Expand Down Expand Up @@ -1062,6 +1070,21 @@ export function SettingsField(props: SettingsFieldProps) {
onChange={(e) => onChange(e.target.value)}
/>,
);
case 'map':
// objectstack#31391 — keyed rows over the served `options`, one cell per
// declared column. A group named by the row's label, so no `htmlFor`.
return wrapper(
<SettingsMapField
spec={spec}
value={value}
onChange={onChange}
disabled={disabled}
labelId={labelId}
optionLabel={renderOptionLabel}
errorAt={error ? errorAt : undefined}
/>,
undefined,
);
default:
return wrapper(
<div className="text-sm text-muted-foreground">
Expand Down
Loading
Loading