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
22 changes: 22 additions & 0 deletions .changeset/retire-compactlayout-alias.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
'@objectstack/spec': minor
'@objectstack/plugin-audit': patch
'@objectstack/plugin-approvals': patch
'@objectstack/plugin-security': patch
'@objectstack/plugin-sharing': patch
'@objectstack/plugin-webhooks': patch
'@objectstack/service-storage': patch
'@objectstack/service-automation': patch
'@objectstack/service-messaging': patch
'@objectstack/service-realtime': patch
---

Retire the deprecated `compactLayout` alias for `highlightFields` (framework#2536, closes the ADR-0085 deprecation window).

- `ObjectSchema` no longer declares `compactLayout`: `create()` rejects it like any unknown key; lenient `parse()` strips it (no silent aliasing).
- The parse-time alias AND the `highlightFields → compactLayout` back-fill transition mirror are removed from `normalizeSemanticRoleAliases`. Served metadata now carries the canonical key only.
- All remaining first-party authors (27 system objects across plugin-audit / approvals / security / sharing / webhooks / service-storage / automation / messaging / realtime — missed by the #2521 sweep, caught by the type gate) renamed to `highlightFields`.
- The downstream smoke pin moves to hotcrm v1.2.2 (hotcrm#424: same rename + deps ^11.7.0).
- Consumers were switched in objectui#2168 and shipped via the console pin bump (#2526); this closes the window scheduled there. The dogfood mirror assertion (#2528) flips to `compactLayout: undefined` in this same change, per the plan it carried.

Version note: minor, not major — the key was deprecated-with-alias for a full release window, all first-party consumers/authors are migrated, and the spec api-surface gate reports no export changes (same documented-exception path as the ADR-0085 removals in 11.7.0). External metadata still authoring `compactLayout` will now fail `create()` loudly with the standard unknown-key error naming the key.
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ jobs:
# ADR-0085 (hotcrm#423) — the intended break, explicitly digested
# downstream. Bump this ref whenever a deliberate spec surface
# removal ships a matching hotcrm release.
HOTCRM_REF: v1.2.1
HOTCRM_REF: v1.2.2
run: bash scripts/downstream-smoke.sh

- name: Create Release Pull Request or Publish to npm
Expand Down
23 changes: 12 additions & 11 deletions examples/app-showcase/src/objects/semantic-zoo.object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@
* strips nor mangles them. Guarded by
* `packages/dogfood/test/semantic-roles.dogfood.test.ts`.
*
* Two objects because the alias runs both directions:
* - `SemanticZoo` authors the CANONICAL spellings (highlightFields,
* stageField: 'status', collapse enum) — served meta must also carry the
* deprecated `compactLayout` mirror for pre-11.7 renderers.
* - `SemanticZooLegacy` authors the DEPRECATED spelling (compactLayout) and
* `stageField: false` — served meta must carry the canonical
* `highlightFields`, and `false` must survive (it is the only "stop
* guessing" signal; a falsy-check regression turns the stepper back on).
* Two objects, two role postures:
* - `SemanticZoo` authors the full canonical role set (highlightFields,
* stageField: 'status', collapse enum).
* - `SemanticZooLegacy` carries `stageField: false` — `false` must survive
* serialization strictly (it is the only "stop guessing" signal; a
* falsy-check regression turns the stepper back on). It exercised the
* deprecated `compactLayout` alias during the ADR-0085 window; the alias
* was retired by framework#2536.
*/
import { ObjectSchema, Field } from '@objectstack/spec/data';

Expand Down Expand Up @@ -55,7 +55,7 @@ export const SemanticZooLegacy = ObjectSchema.create({
label: 'Semantic Zoo (Legacy)',
pluralLabel: 'Semantic Zoo Legacies',
icon: 'flask-round',
description: 'ADR-0085 semantic-role runtime fixture (deprecated spellings)',
description: 'ADR-0085 semantic-role runtime fixture (stageField:false suppression)',

fields: {
name: Field.text({ label: 'Name', required: true }),
Expand All @@ -71,8 +71,9 @@ export const SemanticZooLegacy = ObjectSchema.create({
amount: Field.number({ label: 'Amount' }),
},

// Deprecated alias on purpose — must surface as highlightFields when served.
compactLayout: ['name', 'amount'],
// (This fixture authored the deprecated `compactLayout` spelling during the
// ADR-0085 alias window; retired by framework#2536.)
highlightFields: ['name', 'amount'],
// This status is a color, not a lifecycle.
stageField: false,
});
15 changes: 6 additions & 9 deletions packages/dogfood/test/semantic-roles.dogfood.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,10 @@ describe('ADR-0085 semantic roles survive the served pipeline', () => {
const def = await servedObject('showcase_semantic_zoo');

expect(def.highlightFields).toEqual(['name', 'status', 'amount']);
// Transition mirror for pre-11.7 renderers (ObjectGrid & friends read the
// old spelling). Deliberately asserted: when the deprecated alias is
// retired, this expectation is DELETED IN THE SAME PR — a red here after
// that removal means the retirement missed a consumer.
expect(def.compactLayout).toEqual(['name', 'status', 'amount']);
// Alias retired (framework#2536): served meta carries the canonical key
// ONLY. This flip happened in the same PR that removed the transition
// mirror, per the plan the mirror assertion carried.
expect(def.compactLayout).toBeUndefined();

expect(def.stageField).toBe('status');

Expand All @@ -62,13 +61,11 @@ describe('ADR-0085 semantic roles survive the served pipeline', () => {
expect(def.fields?.amount?.group).toBe('money');
});

it('deprecated spellings alias onto the canonical keys when served', async () => {
it('the legacy fixture serves canonical highlightFields only (alias retired)', async () => {
const def = await servedObject('showcase_semantic_zoo_legacy');

// compactLayout (deprecated) must surface as highlightFields…
expect(def.highlightFields).toEqual(['name', 'amount']);
// …and stay readable under the old name (ADR-0079 alias pattern).
expect(def.compactLayout).toEqual(['name', 'amount']);
expect(def.compactLayout).toBeUndefined();
});

it('stageField:false survives serialization as a strict false', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const SysApprovalAction = ObjectSchema.create({
displayNameField: 'id',
nameField: 'id', // [ADR-0079] canonical primary-title pointer (mirrors deprecated displayNameField)
titleFormat: '{action} · {step_name}',
compactLayout: ['request_id', 'step_name', 'action', 'actor_id', 'created_at'],
highlightFields: ['request_id', 'step_name', 'action', 'actor_id', 'created_at'],

listViews: {
recent: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const SysApprovalApprover = ObjectSchema.create({
displayNameField: 'id',
nameField: 'id', // [ADR-0079] canonical primary-title pointer (mirrors deprecated displayNameField)
titleFormat: '{approver} · {request_id}',
compactLayout: ['request_id', 'approver', 'created_at'],
highlightFields: ['request_id', 'approver', 'created_at'],

fields: {
id: Field.text({ label: 'Row ID', required: true, readonly: true, group: 'System' }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const SysApprovalRequest = ObjectSchema.create({
displayNameField: 'id',
nameField: 'id', // [ADR-0079] canonical primary-title pointer (mirrors deprecated displayNameField)
titleFormat: '{process_name} · {record_id}',
compactLayout: ['process_name', 'object_name', 'record_id', 'status', 'current_step', 'submitter_id', 'updated_at'],
highlightFields: ['process_name', 'object_name', 'record_id', 'status', 'current_step', 'submitter_id', 'updated_at'],

// Curated built-in list views — render as segmented tabs in the console.
// Filters use {current_user_id} substitution wired by the console.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const SysActivity = ObjectSchema.create({
displayNameField: 'summary',
nameField: 'summary', // [ADR-0079] canonical primary-title pointer (mirrors deprecated displayNameField)
titleFormat: '{type} · {summary}',
compactLayout: ['timestamp', 'type', 'actor_name', 'summary'],
highlightFields: ['timestamp', 'type', 'actor_name', 'summary'],

fields: {
id: Field.text({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const SysAuditLog = ObjectSchema.create({
displayNameField: 'action',
nameField: 'action', // [ADR-0079] canonical primary-title pointer (mirrors deprecated displayNameField)
titleFormat: '{action} · {object_name}',
compactLayout: ['created_at', 'action', 'object_name', 'record_id', 'user_id'],
highlightFields: ['created_at', 'action', 'object_name', 'record_id', 'user_id'],

listViews: {
recent: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const SysComment = ObjectSchema.create({
displayNameField: 'body',
nameField: 'body', // [ADR-0079] canonical primary-title pointer (mirrors deprecated displayNameField)
titleFormat: '{author_name}: {body}',
compactLayout: ['created_at', 'author_name', 'body'],
highlightFields: ['created_at', 'author_name', 'body'],

fields: {
id: Field.text({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const SysCapability = ObjectSchema.create({
displayNameField: 'label',
nameField: 'label', // [ADR-0079] canonical primary-title pointer (mirrors deprecated displayNameField)
titleFormat: '{label}',
compactLayout: ['label', 'name', 'scope', 'managed_by', 'active'],
highlightFields: ['label', 'name', 'scope', 'managed_by', 'active'],

actions: [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const SysPermissionSet = ObjectSchema.create({
displayNameField: 'label',
nameField: 'label', // [ADR-0079] canonical primary-title pointer (mirrors deprecated displayNameField)
titleFormat: '{label}',
compactLayout: ['label', 'name', 'active'],
highlightFields: ['label', 'name', 'active'],

// Custom actions — permission sets are templates assigned to roles or
// users (via sys_role_permission_set / sys_user_permission_set). The
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const SysRolePermissionSet = ObjectSchema.create({
managedBy: 'system',
description: 'Binds a permission set to a role.',
titleFormat: '{role_id} → {permission_set_id}',
compactLayout: ['role_id', 'permission_set_id'],
highlightFields: ['role_id', 'permission_set_id'],

fields: {
id: Field.text({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const SysRole = ObjectSchema.create({
displayNameField: 'label',
nameField: 'label', // [ADR-0079] canonical primary-title pointer (mirrors deprecated displayNameField)
titleFormat: '{label}',
compactLayout: ['label', 'name', 'active', 'is_default'],
highlightFields: ['label', 'name', 'active', 'is_default'],

// Custom actions — system roles drive RBAC and are edited rarely but
// require the four high-frequency sysadmin affordances every IdP
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const SysUserPermissionSet = ObjectSchema.create({
managedBy: 'system',
description: 'Direct assignment of a permission set to a user (optionally scoped to an organization).',
titleFormat: '{user_id} → {permission_set_id}',
compactLayout: ['user_id', 'permission_set_id', 'organization_id'],
highlightFields: ['user_id', 'permission_set_id', 'organization_id'],

fields: {
id: Field.text({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const SysUserRole = ObjectSchema.create({
managedBy: 'system',
description: 'Assigns an RBAC role (sys_role.name) to a user. Platform-owned (ADR-0057 D4).',
titleFormat: '{user_id} → {role}',
compactLayout: ['user_id', 'role', 'organization_id'],
highlightFields: ['user_id', 'role', 'organization_id'],

fields: {
id: Field.text({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const SysRecordShare = ObjectSchema.create({
managedBy: 'system',
description: 'Per-record sharing grant — extends OWD with explicit access',
titleFormat: '{object_name}/{record_id} → {recipient_id} ({access_level})',
compactLayout: ['object_name', 'record_id', 'recipient_id', 'access_level', 'source'],
highlightFields: ['object_name', 'record_id', 'recipient_id', 'access_level', 'source'],

listViews: {
granted_to_me: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export const SysShareLink = ObjectSchema.create({
managedBy: 'system',
description: 'Opaque capability token granting access to a single record. Notion/Figma-style public link sharing.',
titleFormat: '{object_name}/{record_id} ({permission})',
compactLayout: ['object_name', 'record_id', 'permission', 'audience', 'expires_at', 'revoked_at'],
highlightFields: ['object_name', 'record_id', 'permission', 'audience', 'expires_at', 'revoked_at'],

listViews: {
active_links: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const SysSharingRule = ObjectSchema.create({
displayNameField: 'name',
nameField: 'name', // [ADR-0079] canonical primary-title pointer (mirrors deprecated displayNameField)
titleFormat: '{label}',
compactLayout: ['name', 'object_name', 'recipient_type', 'recipient_id', 'access_level', 'active'],
highlightFields: ['name', 'object_name', 'recipient_type', 'recipient_id', 'access_level', 'active'],

listViews: {
active: {
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/plugin-webhooks/src/sys-webhook.object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export const SysWebhook = ObjectSchema.create({
displayNameField: 'name',
nameField: 'name', // [ADR-0079] canonical primary-title pointer (mirrors deprecated displayNameField)
titleFormat: '{label}',
compactLayout: ['name', 'object_name', 'url', 'active', 'updated_at'],
highlightFields: ['name', 'object_name', 'url', 'active', 'updated_at'],

listViews: {
active: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const SysAutomationRun = ObjectSchema.create({
displayNameField: 'id',
nameField: 'id', // [ADR-0079] canonical primary-title pointer (mirrors deprecated displayNameField)
titleFormat: '{flow_name} · {node_id}',
compactLayout: ['flow_name', 'node_id', 'status', 'correlation', 'started_at', 'updated_at'],
highlightFields: ['flow_name', 'node_id', 'status', 'correlation', 'started_at', 'updated_at'],

fields: {
id: Field.text({ label: 'Run ID', required: true, readonly: true, group: 'System' }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const HttpDelivery = ObjectSchema.create({
displayNameField: 'id',
nameField: 'id', // [ADR-0079] canonical primary-title pointer (mirrors deprecated displayNameField)
titleFormat: '{label} → {url}',
compactLayout: ['source', 'url', 'status', 'attempts', 'next_retry_at'],
highlightFields: ['source', 'url', 'status', 'attempts', 'next_retry_at'],

listViews: {
recent: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const InboxMessage = ObjectSchema.create({
description: 'User-facing in-app notification rows materialized by the inbox messaging channel.',
nameField: 'title', // [ADR-0079] canonical primary-title pointer (single-field titleFormat)
titleFormat: '{title}',
compactLayout: ['title', 'user_id', 'severity', 'created_at'],
highlightFields: ['title', 'user_id', 'severity', 'created_at'],

listViews: {
mine: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const NotificationDelivery = ObjectSchema.create({
managedBy: 'system',
description: 'Durable per-recipient × channel delivery outbox (ADR-0030 Layer 4).',
titleFormat: '{channel} → {recipient_id}',
compactLayout: ['notification_id', 'recipient_id', 'channel', 'status', 'attempts'],
highlightFields: ['notification_id', 'recipient_id', 'channel', 'status', 'attempts'],

fields: {
id: Field.text({ label: 'Delivery ID', required: true, readonly: true }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const NotificationPreference = ObjectSchema.create({
managedBy: 'system',
description: 'Per-user × topic × channel notification toggle (mute/allow), with admin-global defaults.',
titleFormat: '{user_id} · {topic} · {channel}',
compactLayout: ['user_id', 'topic', 'channel', 'enabled', 'digest'],
highlightFields: ['user_id', 'topic', 'channel', 'enabled', 'digest'],

fields: {
id: Field.text({ label: 'Preference ID', required: true, readonly: true }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const NotificationReceipt = ObjectSchema.create({
managedBy: 'system',
description: 'Per-recipient × channel receipt; the source of truth for notification read-state.',
titleFormat: '{state}',
compactLayout: ['notification_id', 'user_id', 'channel', 'state', 'at'],
highlightFields: ['notification_id', 'user_id', 'channel', 'state', 'at'],

fields: {
id: Field.text({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const NotificationSubscription = ObjectSchema.create({
managedBy: 'system',
description: 'Standing subscription of a principal (role/team/user) to a notification topic.',
titleFormat: '{principal} · {topic}',
compactLayout: ['topic', 'principal', 'enabled', 'created_at'],
highlightFields: ['topic', 'principal', 'enabled', 'created_at'],

fields: {
id: Field.text({ label: 'Subscription ID', required: true, readonly: true }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const NotificationTemplate = ObjectSchema.create({
managedBy: 'system',
description: 'Per (topic × channel × locale) render template for notifications.',
titleFormat: '{topic} · {channel} · {locale}',
compactLayout: ['topic', 'channel', 'locale', 'is_active'],
highlightFields: ['topic', 'channel', 'locale', 'is_active'],

fields: {
id: Field.text({ label: 'Template ID', required: true, readonly: true }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const SysPresence = ObjectSchema.create({
managedBy: 'append-only',
description: 'Real-time user presence and activity tracking',
titleFormat: '{user_id} ({status})',
compactLayout: ['user_id', 'status', 'last_seen'],
highlightFields: ['user_id', 'status', 'last_seen'],

fields: {
id: Field.text({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const SystemFile = ObjectSchema.create({
description: 'Storage service file metadata (fileId ↔ key mapping)',
nameField: 'name', // [ADR-0079] canonical primary-title pointer (single-field titleFormat)
titleFormat: '{name}',
compactLayout: ['name', 'mime_type', 'size', 'status', 'created_at'],
highlightFields: ['name', 'mime_type', 'size', 'status', 'created_at'],

fields: {
id: Field.text({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const SystemUploadSession = ObjectSchema.create({
description: 'Resumable multipart upload sessions tracked by service-storage',
nameField: 'filename', // [ADR-0079] canonical primary-title pointer (single-field titleFormat)
titleFormat: '{filename}',
compactLayout: ['filename', 'status', 'uploaded_chunks', 'total_chunks', 'expires_at'],
highlightFields: ['filename', 'status', 'uploaded_chunks', 'total_chunks', 'expires_at'],

fields: {
id: Field.text({
Expand Down
4 changes: 0 additions & 4 deletions packages/spec/liveness/object.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,6 @@
"status": "live",
"note": "objectui ({{record.field}} interpolation)."
},
"compactLayout": {
"status": "live",
"note": "[DEPRECATED → highlightFields, ADR-0085] parse-time alias copied onto highlightFields; preserved on output for back-compat."
},
"highlightFields": {
"status": "live",
"note": "ADR-0085 semantic role (renamed from compactLayout): objectui default list/grid columns (ObjectGrid/ObjectView/InterfaceListPage), child-record previews (first entry), record:details auto layout, detail highlight strip (first 4)."
Expand Down
Loading