Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5bda5f0d68 | |||
| 5a1ae372dc | |||
| 33a403c0bd |
@@ -0,0 +1,106 @@
|
||||
name: CI New UI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
merge_group:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
||||
|
||||
jobs:
|
||||
changed-files-check:
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: ./.github/workflows/changed-files.yaml
|
||||
with:
|
||||
files: |
|
||||
package.json
|
||||
yarn.lock
|
||||
packages/twenty-new-ui/**
|
||||
packages/twenty-shared/**
|
||||
new-ui-task:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
task: [lint, typecheck, test]
|
||||
steps:
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Run ${{ matrix.task }}
|
||||
run: npx nx ${{ matrix.task }} twenty-new-ui
|
||||
new-ui-sb-build:
|
||||
needs: changed-files-check
|
||||
if: >-
|
||||
always() &&
|
||||
(github.event_name == 'push' ||
|
||||
needs.changed-files-check.outputs.any_changed == 'true')
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Build storybook
|
||||
run: npx nx storybook:build twenty-new-ui
|
||||
- name: Upload storybook build
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: storybook-twenty-new-ui
|
||||
path: packages/twenty-new-ui/storybook-static
|
||||
retention-days: 1
|
||||
new-ui-sb-test:
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest
|
||||
needs: new-ui-sb-build
|
||||
if: always() && needs.new-ui-sb-build.result == 'success'
|
||||
steps:
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Build dependencies
|
||||
run: npx nx build twenty-shared
|
||||
- name: Download storybook build
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: storybook-twenty-new-ui
|
||||
path: packages/twenty-new-ui/storybook-static
|
||||
- name: Install Playwright
|
||||
run: |
|
||||
cd packages/twenty-new-ui
|
||||
npx playwright install
|
||||
- name: Run storybook tests
|
||||
run: npx nx storybook:test twenty-new-ui
|
||||
- name: Upload screenshots for visual regression
|
||||
if: always() && !cancelled()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: argos-screenshots-twenty-new-ui
|
||||
path: packages/twenty-new-ui/screenshots
|
||||
retention-days: 1
|
||||
ci-new-ui-status-check:
|
||||
if: always() && !cancelled()
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
needs: [changed-files-check, new-ui-task, new-ui-sb-build, new-ui-sb-test]
|
||||
steps:
|
||||
- name: Fail job if any needs failed
|
||||
if: contains(needs.*.result, 'failure')
|
||||
run: exit 1
|
||||
@@ -3,10 +3,13 @@ name: Visual Regression Dispatch
|
||||
# Dispatches visual regression processing to ci-privileged after CI completes.
|
||||
# Runs in the context of the base repo (not the fork) so it has access to secrets,
|
||||
# making it work for external contributor PRs.
|
||||
#
|
||||
# All dispatches use the same event_type=visual-regression with project/artifact_name
|
||||
# in the payload. ci-privileged routes to the correct Argos project based on these.
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ['CI UI']
|
||||
workflows: ['CI UI', 'CI New UI']
|
||||
types: [completed]
|
||||
|
||||
permissions:
|
||||
@@ -15,19 +18,37 @@ permissions:
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
dispatch-pr:
|
||||
if: >-
|
||||
github.event.workflow_run.event == 'pull_request' &&
|
||||
github.event.workflow_run.conclusion == 'success'
|
||||
resolve-context:
|
||||
if: github.event.workflow_run.conclusion == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
outputs:
|
||||
workflow_name: ${{ steps.context.outputs.workflow_name }}
|
||||
artifact_name: ${{ steps.context.outputs.artifact_name }}
|
||||
has_artifact: ${{ steps.check-artifact.outputs.exists }}
|
||||
is_pr: ${{ steps.pr-info.outputs.has_pr }}
|
||||
pr_number: ${{ steps.pr-info.outputs.pr_number }}
|
||||
merge_base_sha: ${{ steps.merge-base.outputs.sha }}
|
||||
steps:
|
||||
- name: Resolve workflow context
|
||||
id: context
|
||||
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
||||
with:
|
||||
script: |
|
||||
const name = context.payload.workflow_run.name;
|
||||
const artifactName = name === 'CI New UI'
|
||||
? 'argos-screenshots-twenty-new-ui'
|
||||
: 'argos-screenshots-twenty-ui';
|
||||
core.setOutput('workflow_name', name);
|
||||
core.setOutput('artifact_name', artifactName);
|
||||
core.info(`Workflow: ${name}, artifact: ${artifactName}`);
|
||||
|
||||
- name: Check if screenshots artifact exists
|
||||
id: check-artifact
|
||||
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
||||
with:
|
||||
script: |
|
||||
const artifactName = 'argos-screenshots-twenty-ui';
|
||||
const artifactName = '${{ steps.context.outputs.artifact_name }}';
|
||||
const runId = context.payload.workflow_run.id;
|
||||
|
||||
const { data: artifacts } = await github.rest.actions.listWorkflowRunArtifacts({
|
||||
@@ -44,7 +65,9 @@ jobs:
|
||||
}
|
||||
|
||||
- name: Get PR number
|
||||
if: steps.check-artifact.outputs.exists == 'true'
|
||||
if: >-
|
||||
steps.check-artifact.outputs.exists == 'true' &&
|
||||
github.event.workflow_run.event == 'pull_request'
|
||||
id: pr-info
|
||||
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
||||
with:
|
||||
@@ -111,73 +134,152 @@ jobs:
|
||||
core.setOutput('sha', '');
|
||||
}
|
||||
|
||||
# ── Dispatch: twenty-ui pixel diff (CI UI, PRs + main) ──
|
||||
dispatch-twenty-ui:
|
||||
needs: resolve-context
|
||||
if: >-
|
||||
needs.resolve-context.outputs.workflow_name == 'CI UI' &&
|
||||
needs.resolve-context.outputs.has_artifact == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Dispatch to ci-privileged
|
||||
if: steps.check-artifact.outputs.exists == 'true' && steps.pr-info.outputs.has_pr == 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.CI_PRIVILEGED_DISPATCH_TOKEN }}
|
||||
PR_NUMBER: ${{ steps.pr-info.outputs.pr_number }}
|
||||
PR_NUMBER: ${{ needs.resolve-context.outputs.pr_number }}
|
||||
WORKFLOW_RUN_ID: ${{ github.event.workflow_run.id }}
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
BRANCH: ${{ github.event.workflow_run.head_branch }}
|
||||
COMMIT: ${{ github.event.workflow_run.head_sha }}
|
||||
REFERENCE_COMMIT: ${{ steps.merge-base.outputs.sha }}
|
||||
REFERENCE_COMMIT: ${{ needs.resolve-context.outputs.merge_base_sha }}
|
||||
ARTIFACT_NAME: ${{ needs.resolve-context.outputs.artifact_name }}
|
||||
run: |
|
||||
ARGS=(
|
||||
--method POST
|
||||
-f event_type=visual-regression
|
||||
-f "client_payload[pr_number]=$PR_NUMBER"
|
||||
-f "client_payload[project]=twenty-ui"
|
||||
-f "client_payload[artifact_name]=$ARTIFACT_NAME"
|
||||
-f "client_payload[run_id]=$WORKFLOW_RUN_ID"
|
||||
-f "client_payload[repo]=$REPOSITORY"
|
||||
-f "client_payload[branch]=$BRANCH"
|
||||
-f "client_payload[commit]=$COMMIT"
|
||||
)
|
||||
|
||||
if [ -n "$PR_NUMBER" ]; then
|
||||
ARGS+=(-f "client_payload[pr_number]=$PR_NUMBER")
|
||||
fi
|
||||
if [ -n "$REFERENCE_COMMIT" ]; then
|
||||
ARGS+=(-f "client_payload[reference_commit]=$REFERENCE_COMMIT")
|
||||
fi
|
||||
|
||||
gh api repos/twentyhq/ci-privileged/dispatches "${ARGS[@]}"
|
||||
|
||||
# ── Dispatch: twenty-new-ui pixel diff (CI New UI, PRs + main) ──
|
||||
dispatch-twenty-new-ui:
|
||||
needs: resolve-context
|
||||
if: >-
|
||||
needs.resolve-context.outputs.workflow_name == 'CI New UI' &&
|
||||
needs.resolve-context.outputs.has_artifact == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Dispatch to ci-privileged
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.CI_PRIVILEGED_DISPATCH_TOKEN }}
|
||||
WORKFLOW_RUN_ID: ${{ github.event.workflow_run.id }}
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
BRANCH: ${{ github.event.workflow_run.head_branch }}
|
||||
COMMIT: ${{ github.event.workflow_run.head_sha }}
|
||||
PR_NUMBER: ${{ needs.resolve-context.outputs.pr_number }}
|
||||
REFERENCE_COMMIT: ${{ needs.resolve-context.outputs.merge_base_sha }}
|
||||
ARTIFACT_NAME: ${{ needs.resolve-context.outputs.artifact_name }}
|
||||
run: |
|
||||
ARGS=(
|
||||
--method POST
|
||||
-f event_type=visual-regression
|
||||
-f "client_payload[project]=twenty-new-ui"
|
||||
-f "client_payload[artifact_name]=$ARTIFACT_NAME"
|
||||
-f "client_payload[run_id]=$WORKFLOW_RUN_ID"
|
||||
-f "client_payload[repo]=$REPOSITORY"
|
||||
-f "client_payload[branch]=$BRANCH"
|
||||
-f "client_payload[commit]=$COMMIT"
|
||||
)
|
||||
|
||||
if [ -n "$PR_NUMBER" ]; then
|
||||
ARGS+=(-f "client_payload[pr_number]=$PR_NUMBER")
|
||||
fi
|
||||
if [ -n "$REFERENCE_COMMIT" ]; then
|
||||
ARGS+=(-f "client_payload[reference_commit]=$REFERENCE_COMMIT")
|
||||
fi
|
||||
|
||||
gh api repos/twentyhq/ci-privileged/dispatches "${ARGS[@]}"
|
||||
|
||||
# ── Dispatch: cross-comparison baseline (CI UI on main → twenty-ui-vs-new-ui) ──
|
||||
dispatch-comparison-baseline:
|
||||
needs: resolve-context
|
||||
if: >-
|
||||
needs.resolve-context.outputs.workflow_name == 'CI UI' &&
|
||||
needs.resolve-context.outputs.has_artifact == 'true' &&
|
||||
github.event.workflow_run.event == 'push' &&
|
||||
github.event.workflow_run.head_branch == 'main'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Dispatch to ci-privileged (comparison baseline)
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.CI_PRIVILEGED_DISPATCH_TOKEN }}
|
||||
WORKFLOW_RUN_ID: ${{ github.event.workflow_run.id }}
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
BRANCH: ${{ github.event.workflow_run.head_branch }}
|
||||
COMMIT: ${{ github.event.workflow_run.head_sha }}
|
||||
ARTIFACT_NAME: ${{ needs.resolve-context.outputs.artifact_name }}
|
||||
run: |
|
||||
gh api repos/twentyhq/ci-privileged/dispatches \
|
||||
--method POST \
|
||||
-f event_type=visual-regression \
|
||||
-f "client_payload[project]=twenty-ui-vs-new-ui" \
|
||||
-f "client_payload[artifact_name]=$ARTIFACT_NAME" \
|
||||
-f "client_payload[run_id]=$WORKFLOW_RUN_ID" \
|
||||
-f "client_payload[repo]=$REPOSITORY" \
|
||||
-f "client_payload[branch]=$BRANCH" \
|
||||
-f "client_payload[commit]=$COMMIT"
|
||||
|
||||
# ── Dispatch: cross-comparison PR (CI New UI on PRs → twenty-ui-vs-new-ui) ──
|
||||
dispatch-comparison-pr:
|
||||
needs: resolve-context
|
||||
if: >-
|
||||
needs.resolve-context.outputs.workflow_name == 'CI New UI' &&
|
||||
needs.resolve-context.outputs.has_artifact == 'true' &&
|
||||
needs.resolve-context.outputs.is_pr == 'true' &&
|
||||
github.event.workflow_run.event == 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Dispatch to ci-privileged (comparison PR)
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.CI_PRIVILEGED_DISPATCH_TOKEN }}
|
||||
WORKFLOW_RUN_ID: ${{ github.event.workflow_run.id }}
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
BRANCH: ${{ github.event.workflow_run.head_branch }}
|
||||
COMMIT: ${{ github.event.workflow_run.head_sha }}
|
||||
PR_NUMBER: ${{ needs.resolve-context.outputs.pr_number }}
|
||||
REFERENCE_COMMIT: ${{ needs.resolve-context.outputs.merge_base_sha }}
|
||||
ARTIFACT_NAME: ${{ needs.resolve-context.outputs.artifact_name }}
|
||||
run: |
|
||||
ARGS=(
|
||||
--method POST
|
||||
-f event_type=visual-regression
|
||||
-f "client_payload[project]=twenty-ui-vs-new-ui"
|
||||
-f "client_payload[artifact_name]=$ARTIFACT_NAME"
|
||||
-f "client_payload[run_id]=$WORKFLOW_RUN_ID"
|
||||
-f "client_payload[repo]=$REPOSITORY"
|
||||
-f "client_payload[branch]=$BRANCH"
|
||||
-f "client_payload[commit]=$COMMIT"
|
||||
-f "client_payload[pr_number]=$PR_NUMBER"
|
||||
)
|
||||
|
||||
if [ -n "$REFERENCE_COMMIT" ]; then
|
||||
ARGS+=(-f "client_payload[reference_commit]=$REFERENCE_COMMIT")
|
||||
fi
|
||||
|
||||
gh api repos/twentyhq/ci-privileged/dispatches "${ARGS[@]}"
|
||||
|
||||
dispatch-main:
|
||||
if: >-
|
||||
github.event.workflow_run.event == 'push' &&
|
||||
github.event.workflow_run.head_branch == 'main' &&
|
||||
github.event.workflow_run.conclusion == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Check if screenshots artifact exists
|
||||
id: check-artifact
|
||||
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
||||
with:
|
||||
script: |
|
||||
const runId = context.payload.workflow_run.id;
|
||||
const { data: artifacts } = await github.rest.actions.listWorkflowRunArtifacts({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
run_id: runId,
|
||||
});
|
||||
|
||||
const found = artifacts.artifacts.some(a => a.name === 'argos-screenshots-twenty-ui');
|
||||
core.setOutput('exists', found ? 'true' : 'false');
|
||||
|
||||
if (!found) {
|
||||
core.info(`Artifact not found in run ${runId} — skipping`);
|
||||
}
|
||||
|
||||
- name: Dispatch to ci-privileged
|
||||
if: steps.check-artifact.outputs.exists == 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.CI_PRIVILEGED_DISPATCH_TOKEN }}
|
||||
WORKFLOW_RUN_ID: ${{ github.event.workflow_run.id }}
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
BRANCH: ${{ github.event.workflow_run.head_branch }}
|
||||
COMMIT: ${{ github.event.workflow_run.head_sha }}
|
||||
run: |
|
||||
gh api repos/twentyhq/ci-privileged/dispatches \
|
||||
--method POST \
|
||||
-f event_type=visual-regression \
|
||||
-f "client_payload[run_id]=$WORKFLOW_RUN_ID" \
|
||||
-f "client_payload[repo]=$REPOSITORY" \
|
||||
-f "client_payload[branch]=$BRANCH" \
|
||||
-f "client_payload[commit]=$COMMIT"
|
||||
|
||||
@@ -215,11 +215,63 @@ Apollo error formatting, and the icon/theme-color pickers tied to Twenty's icon
|
||||
- **Workbench** — Storybook (`@storybook/react-vite`). Every component has stories covering variants, sizes, and states (via `storybook-addon-pseudo-states`), in light and dark, with `autodocs`.
|
||||
- **Functional** — component/interaction tests via `@storybook/addon-vitest` (real browser); unit tests (Jest) for hooks/utilities; coverage gate via `@storybook/addon-coverage`.
|
||||
- **Accessibility** — Storybook a11y addon (axe-core) with `parameters.a11y.test = 'error'` so violations fail CI.
|
||||
- **Visual parity** — visual regression (Chromatic or test-runner image snapshots) plus side-by-side stories rendering the old and new component with identical props; a pixel-diff threshold is the per-component acceptance gate.
|
||||
- **Visual parity** — visual regression via Argos (self-hosted) plus a cross-package comparison project that diffs `twenty-new-ui` stories against `twenty-ui` stories with identical names; a pixel-diff threshold is the per-component acceptance gate. See [Visual regression](#visual-regression) below.
|
||||
- **Performance & size** — `size-limit` per entry point with budgets; tree-shaking fixtures (importing one component must not pull the library); build-time tracking; render benchmarks via React Profiler; load-time via Lighthouse/Playwright on the built Storybook. As one concrete benchmark, a dedicated **stress story** renders a very large number of a single component (e.g. 10,000 buttons) and measures total render time — compared against the `twenty-ui` equivalent and gated against a budget to catch per-instance overhead regressions.
|
||||
|
||||
CI surfaces a per-PR diff table (`twenty-ui` vs `twenty-new-ui`) for size, a11y, and visual changes.
|
||||
|
||||
## Visual regression
|
||||
|
||||
Two Argos projects (on argos.twenty-internal.com) provide visual regression in CI:
|
||||
|
||||
1. **`twenty-new-ui`** — pixel diff of `twenty-new-ui` stories against the `main` branch baseline. Catches regressions introduced by a PR.
|
||||
2. **`twenty-ui-vs-new-ui`** — cross-package comparison. The baseline is always `twenty-ui` screenshots from `main`; PR builds upload `twenty-new-ui` screenshots and diff them against the `twenty-ui` baseline. This shows exactly which components still differ between the two implementations.
|
||||
|
||||
For the cross-package comparison to produce meaningful diffs, stories in `twenty-new-ui` must use the **same title hierarchy** as `twenty-ui` (e.g. `UI/Input/Toggle`).
|
||||
|
||||
### Local visual diff
|
||||
|
||||
Run a pixel diff of `twenty-new-ui` components against `twenty-ui` using the self-hosted Argos instance.
|
||||
|
||||
**Prerequisites:**
|
||||
- AWS SSO configured and logged in (`aws sso login --profile twenty-dev`)
|
||||
- `twenty-infra/super-cli` cloned (sibling of this repo)
|
||||
|
||||
**1. Start the Argos tunnel**
|
||||
|
||||
In the `twenty-infra/super-cli` directory:
|
||||
|
||||
yarn cli argos-tunnel
|
||||
|
||||
This port-forwards the Argos service to `http://127.0.0.1:4002`.
|
||||
Wait until the CLI shows "Argos tunnel is running".
|
||||
|
||||
**2. Set your Argos token**
|
||||
|
||||
Create a `.env` file in `packages/twenty-new-ui/` (gitignored):
|
||||
|
||||
ARGOS_TOKEN=<your-token-from-argos-project-settings>
|
||||
|
||||
**3. Run the visual diff**
|
||||
|
||||
From the repo root:
|
||||
|
||||
npx nx storybook:visual-diff twenty-new-ui
|
||||
|
||||
This builds Storybook, captures screenshots of every story, and uploads
|
||||
them to Argos with build name `<username>/twenty-new-ui`. The diff
|
||||
compares against the latest approved baseline.
|
||||
|
||||
To run `twenty-ui`'s visual diff in the same Argos instance (to build the
|
||||
cross-package comparison baseline):
|
||||
|
||||
npx nx storybook:visual-diff twenty-ui
|
||||
|
||||
**4. View results**
|
||||
|
||||
Open `http://127.0.0.1:4002` in your browser (while the tunnel is running)
|
||||
to review diffs.
|
||||
|
||||
## Build & publishing
|
||||
|
||||
- Vite library mode, dual ESM/CJS, `vite-plugin-dts`, `vite-plugin-svgr`; SCSS via Vite's built-in `sass`; no Babel.
|
||||
@@ -267,7 +319,7 @@ a passing visual-parity diff, and a within-budget size entry.
|
||||
1. Published package name: `twenty-new-ui` now, renamed to `twenty-ui` at cut-over (Phase 6).
|
||||
2. Styling: confirm SCSS Modules vs vanilla-extract vs plain CSS Modules.
|
||||
3. Variants helper: `clsx` + `data-*` vs `cva`.
|
||||
4. Visual regression tooling: Chromatic vs self-hosted image snapshots.
|
||||
4. ~~Visual regression tooling: Chromatic vs self-hosted image snapshots.~~ **Resolved:** Argos (self-hosted at argos.twenty-internal.com). See [Visual regression](#visual-regression).
|
||||
5. How aggressively to drop `framer-motion` in favor of CSS/Base UI transitions.
|
||||
6. Scope of `assets` / `testing` / `json-visualizer`: port verbatim or modernize.
|
||||
7. Where to draw the generic-vs-app-specific line for `modules/ui`, and whether hybrid components live as a headless core in `twenty-new-ui` with a thin app wrapper in `twenty-front`.
|
||||
|
||||
Reference in New Issue
Block a user