Skip to content

Conversation

@FadhlanR
Copy link
Contributor

@FadhlanR FadhlanR commented Jan 6, 2026

Rename title, description, thumbnailURL to be:
cardTitle, cardDescription, cardThumbnailURL

Rename cardInfo.title, cardInfo.description to be:
cardInfo.name, cardInfo.summary

Here is the script to rename the fields in the JSON files in staging and production, I've tested to rename the boxel skills card instances (cardstack/boxel-skills#43).

#!/usr/bin/env bash
set -euo pipefail

ROOT="${1:-.}"
MODE="${2:-dry-run}" # use "apply" to write changes
BACKUP="${3:-yes}"   # "yes" to write .bak files when applying

python3 - <<'PY' "$ROOT" "$MODE" "$BACKUP"
import json
import os
import sys
from pathlib import Path

root = Path(sys.argv[1])
mode = sys.argv[2]
backup = sys.argv[3] == "yes"

rename_map = {
    "title": "cardTitle",
    "description": "cardDescription",
    "thumbnailURL": "cardThumbnailURL",
}

cardinfo_map = {
    "title": "name",
    "description": "summary",
    "thumbnailURL": "cardThumbnailURL",
}

def rename_keys(obj):
    if isinstance(obj, dict):
        new = {}
        for k, v in obj.items():
            nk = k
            # CardInfo nested fields
            if k == "cardInfo" and isinstance(v, dict):
                v = rename_cardinfo(v)
            # CardDef top-level fields
            if k in rename_map:
                nk = rename_map[k]
            new[nk] = rename_keys(v)
        return new
    if isinstance(obj, list):
        return [rename_keys(x) for x in obj]
    return obj

def rename_cardinfo(info):
    new = {}
    for k, v in info.items():
        nk = cardinfo_map.get(k, k)
        new[nk] = rename_keys(v)
    return new

def process_file(path: Path):
    try:
        raw = path.read_text()
        data = json.loads(raw)
    except Exception:
        return False, None

    updated = rename_keys(data)
    if updated == data:
        return False, None

    if mode == "apply":
        if backup:
            path.write_text(raw)
            path.with_suffix(path.suffix + ".bak").write_text(raw)
        path.write_text(json.dumps(updated, ensure_ascii=True, indent=2) + "\n")

    return True, updated

changed = []
for path in root.rglob("*.json"):
    ok, _ = process_file(path)
    if ok:
        changed.append(path)

print(f"Scanned: {root}")
print(f"Changed: {len(changed)}")
if changed:
    for p in changed[:200]:
        print(p)
    if len(changed) > 200:
        print(f"... and {len(changed) - 200} more")
PY

@github-actions
Copy link

github-actions bot commented Jan 6, 2026

Host Test Results

    1 files  ±0      1 suites  ±0   1h 29m 26s ⏱️ -39s
1 641 tests +1  1 622 ✅  - 1  17 💤 +2  1 ❌ ±0  1 🔥 ±0 
1 655 runs  +1  1 635 ✅  - 1  17 💤 +2  2 ❌ ±0  1 🔥 ±0 

For more details on these failures and errors, see this check.

Results for commit 2e06ece. ± Comparison against base commit b2f9b85.

This pull request removes 1 and adds 2 tests. Note that renamed tests count towards both.
Chrome ‑ Global error: Uncaught TypeError: Failed to fetch at http://localhost:7357/assets/chunk.87676936a9f50c566053.js, line 153748  While executing test: Integration | card-copy: copy button does not appear when right and left stacks are both index cards but there are selections on both sides 
Chrome ‑ Global error: Uncaught TypeError: Failed to fetch at http://localhost:7357/assets/chunk.8cdbc1ba9d70d79e00bc.js, line 153748  While executing test: Integration | card-copy: copy button does not appear when right and left stacks are both single cards items 
Chrome ‑ Integration | card-copy: copy button does not appear when right and left stacks are both the same index card
This pull request skips 2 tests.
Chrome ‑ Integration | ai-assistant-panel | commands: command that returns a CardForAttachmentCard result is specially handled to attach the card
Chrome ‑ Integration | ai-assistant-panel | commands: command that returns a FileForAttachmentCard result is specially handled to attach the file

♻️ This comment has been updated with latest results.

@jurgenwerk jurgenwerk requested a review from Copilot January 6, 2026 14:53
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR renames several field names in the CardDef schema for improved clarity and consistency. The primary change is renaming generic field names like title, description, and thumbnailURL to more specific names prefixed with "card" to better distinguish them from other similar fields.

Key changes:

  • Renamed titlecardTitle
  • Renamed descriptioncardDescription
  • Renamed thumbnailURLcardThumbnailURL

Reviewed changes

Copilot reviewed 299 out of 2009 changed files in this pull request and generated no comments.

File Description
JSON configuration files (catalog-realm/*) Updated field names in card instance data across hundreds of card definitions
TypeScript/GTS template files (base/*) Updated field references in card class definitions and component templates
3d-model-viewer files Updated field names in GLTF viewer component and sample data
boxel-motion test files Updated field references in test component templates

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@jurgenwerk
Copy link
Contributor

@copilot can you review all 2000+ files?

Copy link
Contributor

Copilot AI commented Jan 6, 2026

@jurgenwerk I've opened a new pull request, #3801, to work on those changes. Once the pull request is ready, I'll request review from you.

@lukemelia
Copy link
Contributor

@FadhlanR Is there a plan for card JSON in realms?

@FadhlanR
Copy link
Contributor Author

FadhlanR commented Jan 6, 2026

@FadhlanR Is there a plan for card JSON in realms?

Do you mean the user realms in staging and production? We either need a script to check each JSON file and update them accordingly, or we can ignore them, leaving the new fields with null value.

@lukemelia
Copy link
Contributor

Yes user realms on staging, prod and local dev

@github-actions
Copy link

github-actions bot commented Jan 8, 2026

@FadhlanR FadhlanR marked this pull request as ready for review January 8, 2026 15:31
@FadhlanR FadhlanR requested review from a team and lukemelia January 8, 2026 15:31
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e44e6ab409

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@lukemelia
Copy link
Contributor

@FadhlanR have you verified that our server environment have a python install capable of running this script?

@FadhlanR
Copy link
Contributor Author

@lukemelia here is the node version, I've tested the dry-run in stagin and it works.

#!/usr/bin/env node
'use strict';

const fs = require('fs/promises');
const path = require('path');

const ROOT = process.argv[2] || '.';
const MODE = process.argv[3] || 'dry-run'; // "apply" to write changes
const BACKUP = (process.argv[4] || 'yes') === 'yes';

const renameMap = {
  title: 'cardTitle',
  description: 'cardDescription',
  thumbnailURL: 'cardThumbnailURL',
};

const cardInfoMap = {
  title: 'name',
  description: 'summary',
  thumbnailURL: 'cardThumbnailURL',
};

function renameKeys(obj) {
  if (Array.isArray(obj)) return obj.map(renameKeys);

  if (obj && typeof obj === 'object') {
    const out = {};
    for (const [k, v0] of Object.entries(obj)) {
      let v = v0;

      // If this is cardInfo and it's an object, rename fields using cardInfoMap
      if (k === 'cardInfo' && v && typeof v === 'object' && !Array.isArray(v)) {
        v = renameCardInfo(v);
      }

      // Rename top-level CardDef fields (and any other objects where these keys appear)
      const nk = renameMap[k] ?? k;
      out[nk] = renameKeys(v);
    }
    return out;
  }

  return obj;
}

function renameCardInfo(info) {
  const out = {};
  for (const [k, v] of Object.entries(info)) {
    const nk = cardInfoMap[k] ?? k;
    out[nk] = renameKeys(v);
  }
  return out;
}

function stableStringify(value) {
  // Match python json.dumps(..., indent=2) style reasonably
  return JSON.stringify(value, null, 2) + '\n';
}

async function* walk(dir) {
  let entries;
  try {
    entries = await fs.readdir(dir, { withFileTypes: true });
  } catch {
    return;
  }

  for (const ent of entries) {
    const full = path.join(dir, ent.name);
    if (ent.isDirectory()) {
      // Skip common noisy dirs (optional; remove if you want everything)
      if (ent.name === 'node_modules' || ent.name === '.git') continue;
      yield* walk(full);
    } else if (ent.isFile() && ent.name.endsWith('.json')) {
      yield full;
    }
  }
}

async function processFile(filePath) {
  let raw;
  let data;
  try {
    raw = await fs.readFile(filePath, 'utf8');
    data = JSON.parse(raw);
  } catch {
    return { changed: false };
  }

  const updated = renameKeys(data);

  // Quick deep-compare by JSON form (good enough for deterministic objects)
  const before = JSON.stringify(data);
  const after = JSON.stringify(updated);
  if (before === after) return { changed: false };

  if (MODE === 'apply') {
    if (BACKUP) {
      await fs.writeFile(filePath + '.bak', raw, 'utf8');
    }
    await fs.writeFile(filePath, stableStringify(updated), 'utf8');
  }

  return { changed: true };
}

(async function main() {
  const changedFiles = [];

  for await (const file of walk(ROOT)) {
    const res = await processFile(file);
    if (res.changed) changedFiles.push(file);
  }

  console.log(`Scanned: ${ROOT}`);
  console.log(`Changed: ${changedFiles.length}`);
  if (changedFiles.length) {
    for (const p of changedFiles.slice(0, 200)) console.log(p);
    if (changedFiles.length > 200) {
      console.log(`... and ${changedFiles.length - 200} more`);
    }
  }
})().catch((err) => {
  console.error(err?.stack || String(err));
  process.exit(1);
});

@habdelra
Copy link
Contributor

habdelra commented Jan 13, 2026

@FadhlanR all of us dev will also need to run this script. can we just add it to a scripts folder in this repo so that it is easy for us to migrate our own envs. make sure to document clearly what we need to do in our own envs after we merge upstream into our envs.

@habdelra
Copy link
Contributor

Also I imagine we need to abandon any AI conversations that might have old card structures in them. we should document that as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants