[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-red-hat-developer-test-placement":3,"mdc--ag9py4-key":34,"related-org-red-hat-developer-test-placement":2334,"related-repo-red-hat-developer-test-placement":2504},{"slug":4,"name":4,"fn":5,"description":6,"org":7,"tags":12,"stars":23,"repoUrl":24,"updatedAt":25,"license":26,"forks":27,"topics":28,"repo":29,"sourceUrl":32,"mdContent":33},"test-placement","propose test placement for RHDH plugins","Propose where to test a change in the RHDH dynamic-plugin ecosystem: which repo (rhdh-plugins, rhdh-plugin-export-overlays, rhdh), which test layer (unit, integration, component, cluster-free E2E, cluster E2E), where the test lives, and how to create it — then write it by mirroring an existing test in that repo. Use when asked \"where should I test this\", \"does this need a cluster\", \"should this be an e2e test\", \"which repo does this test belong in\", \"what test layer\", \"test placement\", \"how do I write a test for this\", \"write a test for X\", \"add a unit\u002Fintegration\u002Fcomponent test\", or when reviewing a test added at the wrong layer.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},"red-hat-developer","Red Hat Developer","https:\u002F\u002Fpexgzepcugksgbtrxkhf.supabase.co\u002Fstorage\u002Fv1\u002Fobject\u002Fpublic\u002Forg-logos\u002Fred-hat-developer.png","redhat-developer",[13,17,20],{"name":14,"slug":15,"type":16},"Engineering","engineering","tag",{"name":18,"slug":19,"type":16},"CI\u002FCD","ci-cd",{"name":21,"slug":22,"type":16},"Testing","testing",15,"https:\u002F\u002Fgithub.com\u002Fredhat-developer\u002Frhdh-skill","2026-07-26T05:47:24.404064",null,28,[],{"repoUrl":24,"stars":23,"forks":27,"topics":30,"description":31},[],"Claude Code skill for RHDH plugin lifecycle management","https:\u002F\u002Fgithub.com\u002Fredhat-developer\u002Frhdh-skill\u002Ftree\u002FHEAD\u002Fskills\u002Ftest-placement","---\nname: test-placement\ndescription: >-\n  Propose where to test a change in the RHDH dynamic-plugin ecosystem: which\n  repo (rhdh-plugins, rhdh-plugin-export-overlays, rhdh), which test layer\n  (unit, integration, component, cluster-free E2E, cluster E2E), where the\n  test lives, and how to create it — then write it by mirroring an existing\n  test in that repo. Use when asked \"where should I test this\", \"does this\n  need a cluster\", \"should this be an e2e test\", \"which repo does this test\n  belong in\", \"what test layer\", \"test placement\", \"how do I write a test\n  for this\", \"write a test for X\", \"add a unit\u002Fintegration\u002Fcomponent test\",\n  or when reviewing a test added at the wrong layer.\n---\n\n# Test Placement Advisor\n\nGiven the context of a change, bug, or new feature in the RHDH dynamic-plugin ecosystem, propose **where** it should be tested: which repository, which test layer, where the test lives, and how to create it. The guiding rule: **pick the cheapest environment that can actually catch the bug — most plugin validation does not need a cluster, and an increasing part doesn't need Docker either.**\n\nConventions in this skill: paths are prefixed with the repo they live in — `rhdh:`, `overlays:` (= rhdh-plugin-export-overlays), `plugins:` (= rhdh-plugins). For broader repo context, consult `..\u002Frhdh\u002Freferences\u002Frhdh-repos.md` (requires the `rhdh` core skill installed alongside; skip if the file is not found). Some harnesses referenced here are **still in review** (see References for PR status). Before recommending a harness or script, verify its path exists on the target repo's `main`; if it doesn't, tell the developer it is pending in the corresponding PR instead of asserting it exists.\n\n## When to Use\n\n- A new plugin, plugin version bump, or plugin config change needs test coverage.\n- A bug escaped to a cluster e2e run and the team wants a cheaper regression test.\n- Reviewing a PR that adds a test at the wrong layer (e.g. a cluster e2e for pure UI logic).\n\n## Step 1 — Gather context (ask if missing)\n\nBefore recommending, establish:\n\n1. **What is being validated?** Plugin logic\u002FUI component · packaging\u002Fpublished artifact · plugin loading\u002Frendering inside RHDH · platform behavior (Helm\u002FOperator\u002Fingress\u002Fauth).\n2. **Where did the change happen?** Plugin source (`rhdh-plugins` or another source repo) · packaging metadata (`rhdh-plugin-export-overlays`) · the RHDH app itself (`rhdh`).\n3. **Does verifying it require a rendered UI?** (clicks, headings, navigation)\n4. **Does it require real external services?** (GitHub, Keycloak, LDAP, managed DBs)\n5. **Does it require cluster infrastructure?** (ConfigMap reload, routes, operator, pod logs, port-forward)\n\n## Step 2 — Decision table\n\n| The dev wants to verify… | Repo | Test type \u002F harness | Cluster? | Docker? |\n| --- | --- | --- | --- | --- |\n| Plugin logic \u002F components \u002F API | `rhdh-plugins` (or the plugin's source repo) | unit + component tests, dev app (`yarn start`) | no | no |\n| The plugin still builds as a dynamic plugin | `rhdh-plugins` | `npx @red-hat-developer-hub\u002Fcli plugin export` | no | no |\n| The **published OCI artifact** installs and the **backend plugin boots** | `rhdh-plugin-export-overlays` | native smoke harness (`overlays: smoke-tests-native\u002F`) | no | **no** |\n| All plugins of a **workspace** boot together | `rhdh-plugin-export-overlays` | native smoke with a `dynamic-plugins.yaml` listing the workspace's `oci:\u002F\u002F` refs | no | **no** |\n| A frontend artifact ships its bundle (`dist-scalprum\u002F`, `plugin-manifest.json`) | `rhdh-plugin-export-overlays` | native smoke presence check | no | **no** |\n| The plugin **loads inside the real RHDH app** and the **UI renders** | `rhdh` | cluster-free E2E harness (`rhdh: e2e-tests\u002Fplaywright.legacy-local.config.ts`, script `e2e:legacy-local`) | no | no |\n| Every plugin in the **catalog index** is sane | `rhdh` | catalog-index plugin sanity check (nightly) | no | no |\n| RHDH **container** behavior (image entrypoint, install script inside the image) | `rhdh-plugin-export-overlays` (artifacts) \u002F any (manual) | Docker smoke (`overlays: smoke-tests\u002F` + workflow `run-workspace-smoke-tests.yaml`) · manual: [rhdh-local](https:\u002F\u002Fgithub.com\u002Fredhat-developer\u002Frhdh-local) | no | yes |\n| Helm chart \u002F Operator \u002F ingress \u002F real Keycloak \u002F RBAC on OCP | `rhdh` e2e or `overlays: workspaces\u002F*\u002Fe2e-tests` | cluster e2e | **yes** | n\u002Fa (cluster) |\n\nRule of thumb: **source bugs → rhdh-plugins · artifact bugs → overlays · integration\u002Frender bugs → rhdh · platform bugs → cluster e2e.** If a bug is catchable in more than one place, test it in the cheapest one and don't duplicate downstream.\n\n## Step 3 — Layer ladder (cheapest that catches the bug wins)\n\n| Layer | Scope | Tooling | Cluster | Typical time |\n| --- | --- | --- | --- | --- |\n| **L1** Unit | Pure functions \u002F logic | Jest\u002FVitest | no | ms–s |\n| **L2** Integration | Backend module + plugin API, mocked external deps | `startTestBackend` + supertest | no | s |\n| **L3** Component | React component\u002Fpage with a test harness | RTL + dev server | no | s–min |\n| **L4a** E2E cluster-free | Full app, no managed infra | Playwright + local harness | no | min |\n| **L4b** E2E full | Real OCP\u002FK8s, managed DBs, real IdPs | Playwright + cluster | **yes** | min–h |\n\nThe full per-spec classification of the RHDH e2e suite lives in `rhdh: docs\u002Fe2e-tests\u002Flayer-migration-matrix.md` (see References) — consult it before adding or migrating an e2e spec.\n\n## Step 4 — How to create the test (per placement)\n\n### `rhdh-plugins` (or other source repo) — plugin correctness\n\n- **Where:** `plugins: workspaces\u002F\u003Cworkspace>\u002Fplugins\u002F\u003Cplugin>\u002Fsrc\u002F**` next to the code, following that workspace's existing test setup (Jest\u002FRTL; MSW for API mocks).\n- **How:** copy the pattern of a neighboring `*.test.ts(x)`; run with the workspace's `yarn test`. Manual verification via the workspace dev app (`yarn start`).\n- Also validate packaging when the plugin's build config changed: `npx @red-hat-developer-hub\u002Fcli plugin export` must produce `dist-dynamic\u002F` (+ `dist-scalprum\u002F` for frontend).\n- **Cannot do here:** validate the *published* OCI artifact, or integration with RHDH's app shell\u002Fshared deps.\n\n### `rhdh-plugin-export-overlays` — the published artifact\n\n- **Where:** the native smoke harness, `overlays: smoke-tests-native\u002F`. It installs plugins from OCI with the real `install-dynamic-plugins` CLI and boots backend plugins via `startTestBackend`.\n- **How:** write a `dynamic-plugins.yaml` listing the `oci:\u002F\u002F` artifact refs to validate (a workspace's refs are the `spec.dynamicArtifact` values in `overlays: workspaces\u002F\u003Cname>\u002Fmetadata\u002F*.yaml`), then run `yarn smoke --dynamic-plugins \u003Cfile> [--out results.json]`. Exit code 0 = pass; non-zero writes `results.json` detailing `fail-load` \u002F `fail-start`. CI: `overlays: .github\u002Fworkflows\u002Fnative-smoke.yaml`.\n- **Known gaps (don't fight them):** catalog-extending backend modules don't boot in a minimal `startTestBackend` (upstream `catalog-backend` registration issue — stay on the Docker smoke until fixed); plugins whose `dynamicArtifact` is a local `.\u002Fdynamic-plugins\u002Fdist\u002F...` path ship inside the RHDH image and have no OCI artifact to validate here.\n- **Never add UI-render tests here** — this repo has no app to render into. Delegate rendering to the `rhdh` cluster-free harness.\n- The Docker smoke it replaces (backend scope) lives at `overlays: smoke-tests\u002F` + workflow `run-workspace-smoke-tests.yaml`.\n- `overlays: workspaces\u002F*\u002Fe2e-tests` (cluster, Playwright + `e2e-test-utils`) exists for plugins whose value *is* cluster integration (topology, tekton, argocd…). Don't add one for UI-only behavior.\n\n### `rhdh` — the real app\n\nAuthoring reference for L1–L3: **`rhdh: docs\u002Ftesting.md`** — which utilities to import per layer, what to assert, and the current worked examples. It lives next to the code, so prefer it over any API name written here.\n\nListed cheapest-first, matching the ladder in Step 3.\n\n**L1 unit** (`rhdh: plugins\u002F*\u002Fsrc\u002F**`, `rhdh: packages\u002Fapp\u002Fsrc\u002F**`) — pure logic, or a router you construct by hand and drive with `supertest`; no backend boot. Template to copy: `rhdh: plugins\u002Fscalprum-backend\u002Fsrc\u002Fservice\u002Frouter.test.ts` (table-driven, mock directory for on-disk plugin content).\n\n**L2 backend integration** (`rhdh: plugins\u002F*\u002Fsrc\u002F**`, named `*.integration.test.ts`) — boots the **real plugin** via `startTestBackend` and asserts over HTTP. This is the layer that proves the plugin's wiring — routes, service dependencies, auth policy — which L1 cannot show. Template to copy: `rhdh: plugins\u002Fscalprum-backend\u002Fsrc\u002Fservice\u002Frouter.integration.test.ts`. Assert the HTTP contract (status, body shape, what an unauthenticated caller gets), *not* that the plugin booted — a successful request already implies that. Booting is slow; allow a generous per-test timeout.\n\n**L3 component tests** (`rhdh: packages\u002Fapp\u002Fsrc\u002F**\u002F*.test.tsx`) — page-level RTL compositions, pattern established under RHIDP-13235 in [rhdh#4864](https:\u002F\u002Fgithub.com\u002Fredhat-developer\u002Frhdh\u002Fpull\u002F4864). Existing templates to copy: `InfoCard.test.tsx`, `LearningPathsPage.test.tsx`, `CustomSidebarItem.test.tsx` (component compositions) and `getMountPointData.test.ts` (dynamic-UI helper). Prefer L3 over L4a when no dynamic-plugin loading is involved.\n\n**L4a cluster-free harness** (config `rhdh: e2e-tests\u002Fplaywright.legacy-local.config.ts`, docs `rhdh: docs\u002Fe2e-tests\u002Flocal-e2e-harness.md`) — the only cheap place a frontend dynamic plugin can be *rendered*. To enable a spec\u002Ftest:\n\n1. Plugin not yet installed by the harness? Add its OCI entry to `rhdh: e2e-tests\u002Flocal-harness\u002Fdynamic-plugins.yaml` (tags on `ghcr.io\u002Fredhat-developer\u002Frhdh-plugin-export-overlays\u002F\u003Cpackage>`). If its mount-point config is not in the repo's static `app-config.dynamic-plugins.yaml`, attach the plugin's **canonical `pluginConfig`** (source of truth: `plugins: workspaces\u002F\u003Cws>\u002Fplugins\u002F\u003Cplugin>\u002Fapp-config.dynamic.yaml`) — the harness loads the generated `dynamic-plugins-root\u002Fapp-config.dynamic-plugins.yaml` last, exactly like the production container.\n2. Config that only exists in CI config maps (`rhdh: .ci\u002Fpipelines\u002Fresources\u002Fconfig_map\u002F*`)? Mirror just the needed keys into `rhdh: app-config.local-e2e.yaml` (objects deep-merge; arrays replace).\n3. Tag the test `{ tag: \"@cluster-free\" }` and add its spec file to the config's `testMatch` allowlist.\n4. Repopulate + validate: `.\u002Fe2e-tests\u002Flocal-harness\u002Fpopulate.sh`, then `yarn --cwd e2e-tests e2e:legacy-local` (CI runs this in ~4 min). Requires skopeo — preinstalled in CI; on macOS `brew install skopeo`.\n\n**L4b cluster e2e** (`rhdh: e2e-tests\u002Fplaywright\u002Fe2e\u002F**`) — only when the subject *is* cluster\u002Fplatform behavior or a real external service. Requirements: `component` annotation in `beforeAll` (see the repo's `ci-e2e-testing` rule), correct config map choice (RBAC vs non-RBAC), project registration in `e2e-tests\u002Fplaywright\u002Fprojects.json` if a new project is needed.\n\n**Catalog-index-wide sanity** (nightly, in review — see References) — nothing to write per plugin; it sweeps the whole index.\n\n## Step 5 — Write the test\n\nOnce Steps 2–4 have settled the repo and the layer, write it by **mirroring a real file**, never from memory of an API. The Backstage test surface moves (`@backstage\u002Ftest-utils` was renamed to `@backstage\u002Ffrontend-test-utils`), so a package or helper name recalled from training data is likely to be stale — a file on `main` cannot be.\n\n1. **Open the template** named in Step 4 for the chosen layer and read it in full. If the path no longer exists, glob its directory for a sibling `*.test.ts(x)` and use that; if the directory is gone too, say so rather than inventing imports.\n2. **Consult the repo's own authoring reference** — for `rhdh` L1–L3 that is `rhdh: docs\u002Ftesting.md`, which lists the utilities per layer and what to assert. It is maintained next to the code, so it wins over anything written in this skill.\n3. **Mirror, then adapt** — copy the template's imports, setup and teardown; replace the subject and the assertions. Keep the file next to the code, matching the neighbours' naming (`*.test.ts` for L1, `*.integration.test.ts` for L2).\n4. **Run it** before reporting done:\n\n| Placement | Command |\n| --- | --- |\n| `rhdh` L1 \u002F L2 \u002F L3 | `yarn test --filter=\u003Cpackage>` |\n| `rhdh` L4a | `.\u002Fe2e-tests\u002Flocal-harness\u002Fpopulate.sh` then `yarn --cwd e2e-tests e2e:legacy-local` |\n| `rhdh` L4b | `yarn --cwd e2e-tests playwright test --project=\u003Cproject>` |\n| `rhdh-plugins` | the workspace's `yarn test` |\n| `overlays` native smoke | `yarn smoke --dynamic-plugins \u003Cfile>` |\n\n5. **Layer checklist** — L2: assert the HTTP contract, not that the plugin booted; allow a generous timeout. L4a: tag `{ tag: \"@cluster-free\" }` and add the spec to the config's `testMatch` allowlist. L4b: `component` annotation in `beforeAll`, correct RBAC vs non-RBAC config map, and register the project in `projects.json` if new.\n\n**Justify the test by the failure it would catch, not by coverage.** Codecov is `informational: true` on both the project and patch statuses in `rhdh` and `rhdh-plugins`, so no coverage number can block a PR and there is no threshold to reach. A test whose only rationale is a coverage delta will be — and has been — rejected in review.\n\n## Not possible today (researched — don't burn time)\n\n- **Rendering a frontend dynamic plugin without an RHDH app** — the artifact is a legacy-frontend Scalprum bundle; no standalone host exists, and building one means maintaining a version-coupled \"mini RHDH\".\n- **`@backstage\u002Ffrontend-dynamic-feature-loader` for current plugins** — targets the new frontend system (alpha); our exported plugins are legacy-system bundles. Revisit when app-next matures (RHIDP-15082).\n- **Catalog-extending modules in a minimal `startTestBackend`** — upstream `catalog-backend` issue (see overlays harness notes).\n\n## Output format\n\nAnswer with a concrete recommendation:\n\n- **Repo:** which of the three (or the plugin's own source repo).\n- **Layer \u002F harness:** L1–L4b + the specific harness or suite.\n- **Location:** the directory\u002Ffile where the test goes.\n- **Scaffolding:** the minimal steps or files to create, referencing an existing neighbor as template.\n- **Why not elsewhere:** one line on the layers you rejected (especially if the dev proposed a more expensive one).\n- **Cost:** rough feedback time (seconds \u002F ~4 min cluster-free \u002F cluster job).\n\n## References (PR statuses are as of the last edit — verify before citing as merged)\n\n- Epic **RHIDP-13501** (E2E Test Optimization) — the per-repo responsibility split lives in the epic's comments and its Jira attachment `rhdh-dynamic-plugin-testing-guideline.md` (Jira-only; if unreachable, the decision table above is the summary).\n- Layer matrix: [`rhdh: docs\u002Fe2e-tests\u002Flayer-migration-matrix.md`](https:\u002F\u002Fgithub.com\u002Fredhat-developer\u002Frhdh\u002Fblob\u002Fmain\u002Fdocs\u002Fe2e-tests\u002Flayer-migration-matrix.md) — merged in [rhdh#5044](https:\u002F\u002Fgithub.com\u002Fredhat-developer\u002Frhdh\u002Fpull\u002F5044) (RHIDP-15076). This is a *migration* analysis of the existing e2e suite; for **how to write** an L1–L3 test see the next entry.\n- L1–L3 authoring guide: [`rhdh: docs\u002Ftesting.md`](https:\u002F\u002Fgithub.com\u002Fredhat-developer\u002Frhdh\u002Fblob\u002Fmain\u002Fdocs\u002Ftesting.md) — merged in [rhdh#5140](https:\u002F\u002Fgithub.com\u002Fredhat-developer\u002Frhdh\u002Fpull\u002F5140) (RHIDP-13234). Utilities per layer, what to assert, and the worked examples Step 4 points at.\n- Cluster-free harness + docs: merged in [rhdh#5005](https:\u002F\u002Fgithub.com\u002Fredhat-developer\u002Frhdh\u002Fpull\u002F5005) (RHIDP-15075); expanded to 10 specs \u002F 14 test cases in [rhdh#5057](https:\u002F\u002Fgithub.com\u002Fredhat-developer\u002Frhdh\u002Fpull\u002F5057) — the richest set of worked enablement examples (config mirrors, catalog file locations, OCI plugin additions).\n- Overlays native smoke: merged in [overlays#2714](https:\u002F\u002Fgithub.com\u002Fredhat-developer\u002Frhdh-plugin-export-overlays\u002Fpull\u002F2714); the per-workspace mode used in Step 4 merged in [overlays#2731](https:\u002F\u002Fgithub.com\u002Fredhat-developer\u002Frhdh-plugin-export-overlays\u002Fpull\u002F2731).\n- Catalog-index sanity check: in review in [rhdh#4967](https:\u002F\u002Fgithub.com\u002Fredhat-developer\u002Frhdh\u002Fpull\u002F4967).\n- L3 pattern: merged in [rhdh#4864](https:\u002F\u002Fgithub.com\u002Fredhat-developer\u002Frhdh\u002Fpull\u002F4864) under RHIDP-13235.\n",{"data":35,"body":36},{"name":4,"description":6},{"type":37,"children":38},"root",[39,48,67,128,135,155,161,166,243,249,714,726,732,938,951,957,969,1067,1078,1290,1301,1317,1322,1363,1416,1472,1504,1635,1690,1700,1706,1741,1820,1958,2007,2039,2045,2096,2102,2107,2170,2176],{"type":40,"tag":41,"props":42,"children":44},"element","h1",{"id":43},"test-placement-advisor",[45],{"type":46,"value":47},"text","Test Placement Advisor",{"type":40,"tag":49,"props":50,"children":51},"p",{},[52,54,60,62],{"type":46,"value":53},"Given the context of a change, bug, or new feature in the RHDH dynamic-plugin ecosystem, propose ",{"type":40,"tag":55,"props":56,"children":57},"strong",{},[58],{"type":46,"value":59},"where",{"type":46,"value":61}," it should be tested: which repository, which test layer, where the test lives, and how to create it. The guiding rule: ",{"type":40,"tag":55,"props":63,"children":64},{},[65],{"type":46,"value":66},"pick the cheapest environment that can actually catch the bug — most plugin validation does not need a cluster, and an increasing part doesn't need Docker either.",{"type":40,"tag":49,"props":68,"children":69},{},[70,72,79,81,87,89,95,97,103,105,111,113,118,120,126],{"type":46,"value":71},"Conventions in this skill: paths are prefixed with the repo they live in — ",{"type":40,"tag":73,"props":74,"children":76},"code",{"className":75},[],[77],{"type":46,"value":78},"rhdh:",{"type":46,"value":80},", ",{"type":40,"tag":73,"props":82,"children":84},{"className":83},[],[85],{"type":46,"value":86},"overlays:",{"type":46,"value":88}," (= rhdh-plugin-export-overlays), ",{"type":40,"tag":73,"props":90,"children":92},{"className":91},[],[93],{"type":46,"value":94},"plugins:",{"type":46,"value":96}," (= rhdh-plugins). For broader repo context, consult ",{"type":40,"tag":73,"props":98,"children":100},{"className":99},[],[101],{"type":46,"value":102},"..\u002Frhdh\u002Freferences\u002Frhdh-repos.md",{"type":46,"value":104}," (requires the ",{"type":40,"tag":73,"props":106,"children":108},{"className":107},[],[109],{"type":46,"value":110},"rhdh",{"type":46,"value":112}," core skill installed alongside; skip if the file is not found). Some harnesses referenced here are ",{"type":40,"tag":55,"props":114,"children":115},{},[116],{"type":46,"value":117},"still in review",{"type":46,"value":119}," (see References for PR status). Before recommending a harness or script, verify its path exists on the target repo's ",{"type":40,"tag":73,"props":121,"children":123},{"className":122},[],[124],{"type":46,"value":125},"main",{"type":46,"value":127},"; if it doesn't, tell the developer it is pending in the corresponding PR instead of asserting it exists.",{"type":40,"tag":129,"props":130,"children":132},"h2",{"id":131},"when-to-use",[133],{"type":46,"value":134},"When to Use",{"type":40,"tag":136,"props":137,"children":138},"ul",{},[139,145,150],{"type":40,"tag":140,"props":141,"children":142},"li",{},[143],{"type":46,"value":144},"A new plugin, plugin version bump, or plugin config change needs test coverage.",{"type":40,"tag":140,"props":146,"children":147},{},[148],{"type":46,"value":149},"A bug escaped to a cluster e2e run and the team wants a cheaper regression test.",{"type":40,"tag":140,"props":151,"children":152},{},[153],{"type":46,"value":154},"Reviewing a PR that adds a test at the wrong layer (e.g. a cluster e2e for pure UI logic).",{"type":40,"tag":129,"props":156,"children":158},{"id":157},"step-1-gather-context-ask-if-missing",[159],{"type":46,"value":160},"Step 1 — Gather context (ask if missing)",{"type":40,"tag":49,"props":162,"children":163},{},[164],{"type":46,"value":165},"Before recommending, establish:",{"type":40,"tag":167,"props":168,"children":169},"ol",{},[170,180,213,223,233],{"type":40,"tag":140,"props":171,"children":172},{},[173,178],{"type":40,"tag":55,"props":174,"children":175},{},[176],{"type":46,"value":177},"What is being validated?",{"type":46,"value":179}," Plugin logic\u002FUI component · packaging\u002Fpublished artifact · plugin loading\u002Frendering inside RHDH · platform behavior (Helm\u002FOperator\u002Fingress\u002Fauth).",{"type":40,"tag":140,"props":181,"children":182},{},[183,188,190,196,198,204,206,211],{"type":40,"tag":55,"props":184,"children":185},{},[186],{"type":46,"value":187},"Where did the change happen?",{"type":46,"value":189}," Plugin source (",{"type":40,"tag":73,"props":191,"children":193},{"className":192},[],[194],{"type":46,"value":195},"rhdh-plugins",{"type":46,"value":197}," or another source repo) · packaging metadata (",{"type":40,"tag":73,"props":199,"children":201},{"className":200},[],[202],{"type":46,"value":203},"rhdh-plugin-export-overlays",{"type":46,"value":205},") · the RHDH app itself (",{"type":40,"tag":73,"props":207,"children":209},{"className":208},[],[210],{"type":46,"value":110},{"type":46,"value":212},").",{"type":40,"tag":140,"props":214,"children":215},{},[216,221],{"type":40,"tag":55,"props":217,"children":218},{},[219],{"type":46,"value":220},"Does verifying it require a rendered UI?",{"type":46,"value":222}," (clicks, headings, navigation)",{"type":40,"tag":140,"props":224,"children":225},{},[226,231],{"type":40,"tag":55,"props":227,"children":228},{},[229],{"type":46,"value":230},"Does it require real external services?",{"type":46,"value":232}," (GitHub, Keycloak, LDAP, managed DBs)",{"type":40,"tag":140,"props":234,"children":235},{},[236,241],{"type":40,"tag":55,"props":237,"children":238},{},[239],{"type":46,"value":240},"Does it require cluster infrastructure?",{"type":46,"value":242}," (ConfigMap reload, routes, operator, pod logs, port-forward)",{"type":40,"tag":129,"props":244,"children":246},{"id":245},"step-2-decision-table",[247],{"type":46,"value":248},"Step 2 — Decision table",{"type":40,"tag":250,"props":251,"children":252},"table",{},[253,287],{"type":40,"tag":254,"props":255,"children":256},"thead",{},[257],{"type":40,"tag":258,"props":259,"children":260},"tr",{},[261,267,272,277,282],{"type":40,"tag":262,"props":263,"children":264},"th",{},[265],{"type":46,"value":266},"The dev wants to verify…",{"type":40,"tag":262,"props":268,"children":269},{},[270],{"type":46,"value":271},"Repo",{"type":40,"tag":262,"props":273,"children":274},{},[275],{"type":46,"value":276},"Test type \u002F harness",{"type":40,"tag":262,"props":278,"children":279},{},[280],{"type":46,"value":281},"Cluster?",{"type":40,"tag":262,"props":283,"children":284},{},[285],{"type":46,"value":286},"Docker?",{"type":40,"tag":288,"props":289,"children":290},"tbody",{},[291,332,365,416,471,517,573,609,673],{"type":40,"tag":258,"props":292,"children":293},{},[294,300,310,323,328],{"type":40,"tag":295,"props":296,"children":297},"td",{},[298],{"type":46,"value":299},"Plugin logic \u002F components \u002F API",{"type":40,"tag":295,"props":301,"children":302},{},[303,308],{"type":40,"tag":73,"props":304,"children":306},{"className":305},[],[307],{"type":46,"value":195},{"type":46,"value":309}," (or the plugin's source repo)",{"type":40,"tag":295,"props":311,"children":312},{},[313,315,321],{"type":46,"value":314},"unit + component tests, dev app (",{"type":40,"tag":73,"props":316,"children":318},{"className":317},[],[319],{"type":46,"value":320},"yarn start",{"type":46,"value":322},")",{"type":40,"tag":295,"props":324,"children":325},{},[326],{"type":46,"value":327},"no",{"type":40,"tag":295,"props":329,"children":330},{},[331],{"type":46,"value":327},{"type":40,"tag":258,"props":333,"children":334},{},[335,340,348,357,361],{"type":40,"tag":295,"props":336,"children":337},{},[338],{"type":46,"value":339},"The plugin still builds as a dynamic plugin",{"type":40,"tag":295,"props":341,"children":342},{},[343],{"type":40,"tag":73,"props":344,"children":346},{"className":345},[],[347],{"type":46,"value":195},{"type":40,"tag":295,"props":349,"children":350},{},[351],{"type":40,"tag":73,"props":352,"children":354},{"className":353},[],[355],{"type":46,"value":356},"npx @red-hat-developer-hub\u002Fcli plugin export",{"type":40,"tag":295,"props":358,"children":359},{},[360],{"type":46,"value":327},{"type":40,"tag":295,"props":362,"children":363},{},[364],{"type":46,"value":327},{"type":40,"tag":258,"props":366,"children":367},{},[368,385,393,405,409],{"type":40,"tag":295,"props":369,"children":370},{},[371,373,378,380],{"type":46,"value":372},"The ",{"type":40,"tag":55,"props":374,"children":375},{},[376],{"type":46,"value":377},"published OCI artifact",{"type":46,"value":379}," installs and the ",{"type":40,"tag":55,"props":381,"children":382},{},[383],{"type":46,"value":384},"backend plugin boots",{"type":40,"tag":295,"props":386,"children":387},{},[388],{"type":40,"tag":73,"props":389,"children":391},{"className":390},[],[392],{"type":46,"value":203},{"type":40,"tag":295,"props":394,"children":395},{},[396,398,404],{"type":46,"value":397},"native smoke harness (",{"type":40,"tag":73,"props":399,"children":401},{"className":400},[],[402],{"type":46,"value":403},"overlays: smoke-tests-native\u002F",{"type":46,"value":322},{"type":40,"tag":295,"props":406,"children":407},{},[408],{"type":46,"value":327},{"type":40,"tag":295,"props":410,"children":411},{},[412],{"type":40,"tag":55,"props":413,"children":414},{},[415],{"type":46,"value":327},{"type":40,"tag":258,"props":417,"children":418},{},[419,431,439,460,464],{"type":40,"tag":295,"props":420,"children":421},{},[422,424,429],{"type":46,"value":423},"All plugins of a ",{"type":40,"tag":55,"props":425,"children":426},{},[427],{"type":46,"value":428},"workspace",{"type":46,"value":430}," boot together",{"type":40,"tag":295,"props":432,"children":433},{},[434],{"type":40,"tag":73,"props":435,"children":437},{"className":436},[],[438],{"type":46,"value":203},{"type":40,"tag":295,"props":440,"children":441},{},[442,444,450,452,458],{"type":46,"value":443},"native smoke with a ",{"type":40,"tag":73,"props":445,"children":447},{"className":446},[],[448],{"type":46,"value":449},"dynamic-plugins.yaml",{"type":46,"value":451}," listing the workspace's ",{"type":40,"tag":73,"props":453,"children":455},{"className":454},[],[456],{"type":46,"value":457},"oci:\u002F\u002F",{"type":46,"value":459}," refs",{"type":40,"tag":295,"props":461,"children":462},{},[463],{"type":46,"value":327},{"type":40,"tag":295,"props":465,"children":466},{},[467],{"type":40,"tag":55,"props":468,"children":469},{},[470],{"type":46,"value":327},{"type":40,"tag":258,"props":472,"children":473},{},[474,493,501,506,510],{"type":40,"tag":295,"props":475,"children":476},{},[477,479,485,486,492],{"type":46,"value":478},"A frontend artifact ships its bundle (",{"type":40,"tag":73,"props":480,"children":482},{"className":481},[],[483],{"type":46,"value":484},"dist-scalprum\u002F",{"type":46,"value":80},{"type":40,"tag":73,"props":487,"children":489},{"className":488},[],[490],{"type":46,"value":491},"plugin-manifest.json",{"type":46,"value":322},{"type":40,"tag":295,"props":494,"children":495},{},[496],{"type":40,"tag":73,"props":497,"children":499},{"className":498},[],[500],{"type":46,"value":203},{"type":40,"tag":295,"props":502,"children":503},{},[504],{"type":46,"value":505},"native smoke presence check",{"type":40,"tag":295,"props":507,"children":508},{},[509],{"type":46,"value":327},{"type":40,"tag":295,"props":511,"children":512},{},[513],{"type":40,"tag":55,"props":514,"children":515},{},[516],{"type":46,"value":327},{"type":40,"tag":258,"props":518,"children":519},{},[520,537,545,565,569],{"type":40,"tag":295,"props":521,"children":522},{},[523,525,530,532],{"type":46,"value":524},"The plugin ",{"type":40,"tag":55,"props":526,"children":527},{},[528],{"type":46,"value":529},"loads inside the real RHDH app",{"type":46,"value":531}," and the ",{"type":40,"tag":55,"props":533,"children":534},{},[535],{"type":46,"value":536},"UI renders",{"type":40,"tag":295,"props":538,"children":539},{},[540],{"type":40,"tag":73,"props":541,"children":543},{"className":542},[],[544],{"type":46,"value":110},{"type":40,"tag":295,"props":546,"children":547},{},[548,550,556,558,564],{"type":46,"value":549},"cluster-free E2E harness (",{"type":40,"tag":73,"props":551,"children":553},{"className":552},[],[554],{"type":46,"value":555},"rhdh: e2e-tests\u002Fplaywright.legacy-local.config.ts",{"type":46,"value":557},", script ",{"type":40,"tag":73,"props":559,"children":561},{"className":560},[],[562],{"type":46,"value":563},"e2e:legacy-local",{"type":46,"value":322},{"type":40,"tag":295,"props":566,"children":567},{},[568],{"type":46,"value":327},{"type":40,"tag":295,"props":570,"children":571},{},[572],{"type":46,"value":327},{"type":40,"tag":258,"props":574,"children":575},{},[576,588,596,601,605],{"type":40,"tag":295,"props":577,"children":578},{},[579,581,586],{"type":46,"value":580},"Every plugin in the ",{"type":40,"tag":55,"props":582,"children":583},{},[584],{"type":46,"value":585},"catalog index",{"type":46,"value":587}," is sane",{"type":40,"tag":295,"props":589,"children":590},{},[591],{"type":40,"tag":73,"props":592,"children":594},{"className":593},[],[595],{"type":46,"value":110},{"type":40,"tag":295,"props":597,"children":598},{},[599],{"type":46,"value":600},"catalog-index plugin sanity check (nightly)",{"type":40,"tag":295,"props":602,"children":603},{},[604],{"type":46,"value":327},{"type":40,"tag":295,"props":606,"children":607},{},[608],{"type":46,"value":327},{"type":40,"tag":258,"props":610,"children":611},{},[612,624,634,664,668],{"type":40,"tag":295,"props":613,"children":614},{},[615,617,622],{"type":46,"value":616},"RHDH ",{"type":40,"tag":55,"props":618,"children":619},{},[620],{"type":46,"value":621},"container",{"type":46,"value":623}," behavior (image entrypoint, install script inside the image)",{"type":40,"tag":295,"props":625,"children":626},{},[627,632],{"type":40,"tag":73,"props":628,"children":630},{"className":629},[],[631],{"type":46,"value":203},{"type":46,"value":633}," (artifacts) \u002F any (manual)",{"type":40,"tag":295,"props":635,"children":636},{},[637,639,645,647,653,655],{"type":46,"value":638},"Docker smoke (",{"type":40,"tag":73,"props":640,"children":642},{"className":641},[],[643],{"type":46,"value":644},"overlays: smoke-tests\u002F",{"type":46,"value":646}," + workflow ",{"type":40,"tag":73,"props":648,"children":650},{"className":649},[],[651],{"type":46,"value":652},"run-workspace-smoke-tests.yaml",{"type":46,"value":654},") · manual: ",{"type":40,"tag":656,"props":657,"children":661},"a",{"href":658,"rel":659},"https:\u002F\u002Fgithub.com\u002Fredhat-developer\u002Frhdh-local",[660],"nofollow",[662],{"type":46,"value":663},"rhdh-local",{"type":40,"tag":295,"props":665,"children":666},{},[667],{"type":46,"value":327},{"type":40,"tag":295,"props":669,"children":670},{},[671],{"type":46,"value":672},"yes",{"type":40,"tag":258,"props":674,"children":675},{},[676,681,697,702,709],{"type":40,"tag":295,"props":677,"children":678},{},[679],{"type":46,"value":680},"Helm chart \u002F Operator \u002F ingress \u002F real Keycloak \u002F RBAC on OCP",{"type":40,"tag":295,"props":682,"children":683},{},[684,689,691],{"type":40,"tag":73,"props":685,"children":687},{"className":686},[],[688],{"type":46,"value":110},{"type":46,"value":690}," e2e or ",{"type":40,"tag":73,"props":692,"children":694},{"className":693},[],[695],{"type":46,"value":696},"overlays: workspaces\u002F*\u002Fe2e-tests",{"type":40,"tag":295,"props":698,"children":699},{},[700],{"type":46,"value":701},"cluster e2e",{"type":40,"tag":295,"props":703,"children":704},{},[705],{"type":40,"tag":55,"props":706,"children":707},{},[708],{"type":46,"value":672},{"type":40,"tag":295,"props":710,"children":711},{},[712],{"type":46,"value":713},"n\u002Fa (cluster)",{"type":40,"tag":49,"props":715,"children":716},{},[717,719,724],{"type":46,"value":718},"Rule of thumb: ",{"type":40,"tag":55,"props":720,"children":721},{},[722],{"type":46,"value":723},"source bugs → rhdh-plugins · artifact bugs → overlays · integration\u002Frender bugs → rhdh · platform bugs → cluster e2e.",{"type":46,"value":725}," If a bug is catchable in more than one place, test it in the cheapest one and don't duplicate downstream.",{"type":40,"tag":129,"props":727,"children":729},{"id":728},"step-3-layer-ladder-cheapest-that-catches-the-bug-wins",[730],{"type":46,"value":731},"Step 3 — Layer ladder (cheapest that catches the bug wins)",{"type":40,"tag":250,"props":733,"children":734},{},[735,766],{"type":40,"tag":254,"props":736,"children":737},{},[738],{"type":40,"tag":258,"props":739,"children":740},{},[741,746,751,756,761],{"type":40,"tag":262,"props":742,"children":743},{},[744],{"type":46,"value":745},"Layer",{"type":40,"tag":262,"props":747,"children":748},{},[749],{"type":46,"value":750},"Scope",{"type":40,"tag":262,"props":752,"children":753},{},[754],{"type":46,"value":755},"Tooling",{"type":40,"tag":262,"props":757,"children":758},{},[759],{"type":46,"value":760},"Cluster",{"type":40,"tag":262,"props":762,"children":763},{},[764],{"type":46,"value":765},"Typical time",{"type":40,"tag":288,"props":767,"children":768},{},[769,801,839,871,903],{"type":40,"tag":258,"props":770,"children":771},{},[772,782,787,792,796],{"type":40,"tag":295,"props":773,"children":774},{},[775,780],{"type":40,"tag":55,"props":776,"children":777},{},[778],{"type":46,"value":779},"L1",{"type":46,"value":781}," Unit",{"type":40,"tag":295,"props":783,"children":784},{},[785],{"type":46,"value":786},"Pure functions \u002F logic",{"type":40,"tag":295,"props":788,"children":789},{},[790],{"type":46,"value":791},"Jest\u002FVitest",{"type":40,"tag":295,"props":793,"children":794},{},[795],{"type":46,"value":327},{"type":40,"tag":295,"props":797,"children":798},{},[799],{"type":46,"value":800},"ms–s",{"type":40,"tag":258,"props":802,"children":803},{},[804,814,819,830,834],{"type":40,"tag":295,"props":805,"children":806},{},[807,812],{"type":40,"tag":55,"props":808,"children":809},{},[810],{"type":46,"value":811},"L2",{"type":46,"value":813}," Integration",{"type":40,"tag":295,"props":815,"children":816},{},[817],{"type":46,"value":818},"Backend module + plugin API, mocked external deps",{"type":40,"tag":295,"props":820,"children":821},{},[822,828],{"type":40,"tag":73,"props":823,"children":825},{"className":824},[],[826],{"type":46,"value":827},"startTestBackend",{"type":46,"value":829}," + supertest",{"type":40,"tag":295,"props":831,"children":832},{},[833],{"type":46,"value":327},{"type":40,"tag":295,"props":835,"children":836},{},[837],{"type":46,"value":838},"s",{"type":40,"tag":258,"props":840,"children":841},{},[842,852,857,862,866],{"type":40,"tag":295,"props":843,"children":844},{},[845,850],{"type":40,"tag":55,"props":846,"children":847},{},[848],{"type":46,"value":849},"L3",{"type":46,"value":851}," Component",{"type":40,"tag":295,"props":853,"children":854},{},[855],{"type":46,"value":856},"React component\u002Fpage with a test harness",{"type":40,"tag":295,"props":858,"children":859},{},[860],{"type":46,"value":861},"RTL + dev server",{"type":40,"tag":295,"props":863,"children":864},{},[865],{"type":46,"value":327},{"type":40,"tag":295,"props":867,"children":868},{},[869],{"type":46,"value":870},"s–min",{"type":40,"tag":258,"props":872,"children":873},{},[874,884,889,894,898],{"type":40,"tag":295,"props":875,"children":876},{},[877,882],{"type":40,"tag":55,"props":878,"children":879},{},[880],{"type":46,"value":881},"L4a",{"type":46,"value":883}," E2E cluster-free",{"type":40,"tag":295,"props":885,"children":886},{},[887],{"type":46,"value":888},"Full app, no managed infra",{"type":40,"tag":295,"props":890,"children":891},{},[892],{"type":46,"value":893},"Playwright + local harness",{"type":40,"tag":295,"props":895,"children":896},{},[897],{"type":46,"value":327},{"type":40,"tag":295,"props":899,"children":900},{},[901],{"type":46,"value":902},"min",{"type":40,"tag":258,"props":904,"children":905},{},[906,916,921,926,933],{"type":40,"tag":295,"props":907,"children":908},{},[909,914],{"type":40,"tag":55,"props":910,"children":911},{},[912],{"type":46,"value":913},"L4b",{"type":46,"value":915}," E2E full",{"type":40,"tag":295,"props":917,"children":918},{},[919],{"type":46,"value":920},"Real OCP\u002FK8s, managed DBs, real IdPs",{"type":40,"tag":295,"props":922,"children":923},{},[924],{"type":46,"value":925},"Playwright + cluster",{"type":40,"tag":295,"props":927,"children":928},{},[929],{"type":40,"tag":55,"props":930,"children":931},{},[932],{"type":46,"value":672},{"type":40,"tag":295,"props":934,"children":935},{},[936],{"type":46,"value":937},"min–h",{"type":40,"tag":49,"props":939,"children":940},{},[941,943,949],{"type":46,"value":942},"The full per-spec classification of the RHDH e2e suite lives in ",{"type":40,"tag":73,"props":944,"children":946},{"className":945},[],[947],{"type":46,"value":948},"rhdh: docs\u002Fe2e-tests\u002Flayer-migration-matrix.md",{"type":46,"value":950}," (see References) — consult it before adding or migrating an e2e spec.",{"type":40,"tag":129,"props":952,"children":954},{"id":953},"step-4-how-to-create-the-test-per-placement",[955],{"type":46,"value":956},"Step 4 — How to create the test (per placement)",{"type":40,"tag":958,"props":959,"children":961},"h3",{"id":960},"rhdh-plugins-or-other-source-repo-plugin-correctness",[962,967],{"type":40,"tag":73,"props":963,"children":965},{"className":964},[],[966],{"type":46,"value":195},{"type":46,"value":968}," (or other source repo) — plugin correctness",{"type":40,"tag":136,"props":970,"children":971},{},[972,990,1022,1049],{"type":40,"tag":140,"props":973,"children":974},{},[975,980,982,988],{"type":40,"tag":55,"props":976,"children":977},{},[978],{"type":46,"value":979},"Where:",{"type":46,"value":981}," ",{"type":40,"tag":73,"props":983,"children":985},{"className":984},[],[986],{"type":46,"value":987},"plugins: workspaces\u002F\u003Cworkspace>\u002Fplugins\u002F\u003Cplugin>\u002Fsrc\u002F**",{"type":46,"value":989}," next to the code, following that workspace's existing test setup (Jest\u002FRTL; MSW for API mocks).",{"type":40,"tag":140,"props":991,"children":992},{},[993,998,1000,1006,1008,1014,1016,1021],{"type":40,"tag":55,"props":994,"children":995},{},[996],{"type":46,"value":997},"How:",{"type":46,"value":999}," copy the pattern of a neighboring ",{"type":40,"tag":73,"props":1001,"children":1003},{"className":1002},[],[1004],{"type":46,"value":1005},"*.test.ts(x)",{"type":46,"value":1007},"; run with the workspace's ",{"type":40,"tag":73,"props":1009,"children":1011},{"className":1010},[],[1012],{"type":46,"value":1013},"yarn test",{"type":46,"value":1015},". Manual verification via the workspace dev app (",{"type":40,"tag":73,"props":1017,"children":1019},{"className":1018},[],[1020],{"type":46,"value":320},{"type":46,"value":212},{"type":40,"tag":140,"props":1023,"children":1024},{},[1025,1027,1032,1034,1040,1042,1047],{"type":46,"value":1026},"Also validate packaging when the plugin's build config changed: ",{"type":40,"tag":73,"props":1028,"children":1030},{"className":1029},[],[1031],{"type":46,"value":356},{"type":46,"value":1033}," must produce ",{"type":40,"tag":73,"props":1035,"children":1037},{"className":1036},[],[1038],{"type":46,"value":1039},"dist-dynamic\u002F",{"type":46,"value":1041}," (+ ",{"type":40,"tag":73,"props":1043,"children":1045},{"className":1044},[],[1046],{"type":46,"value":484},{"type":46,"value":1048}," for frontend).",{"type":40,"tag":140,"props":1050,"children":1051},{},[1052,1057,1059,1065],{"type":40,"tag":55,"props":1053,"children":1054},{},[1055],{"type":46,"value":1056},"Cannot do here:",{"type":46,"value":1058}," validate the ",{"type":40,"tag":1060,"props":1061,"children":1062},"em",{},[1063],{"type":46,"value":1064},"published",{"type":46,"value":1066}," OCI artifact, or integration with RHDH's app shell\u002Fshared deps.",{"type":40,"tag":958,"props":1068,"children":1070},{"id":1069},"rhdh-plugin-export-overlays-the-published-artifact",[1071,1076],{"type":40,"tag":73,"props":1072,"children":1074},{"className":1073},[],[1075],{"type":46,"value":203},{"type":46,"value":1077}," — the published artifact",{"type":40,"tag":136,"props":1079,"children":1080},{},[1081,1112,1190,1231,1248,1265],{"type":40,"tag":140,"props":1082,"children":1083},{},[1084,1088,1090,1095,1097,1103,1105,1110],{"type":40,"tag":55,"props":1085,"children":1086},{},[1087],{"type":46,"value":979},{"type":46,"value":1089}," the native smoke harness, ",{"type":40,"tag":73,"props":1091,"children":1093},{"className":1092},[],[1094],{"type":46,"value":403},{"type":46,"value":1096},". It installs plugins from OCI with the real ",{"type":40,"tag":73,"props":1098,"children":1100},{"className":1099},[],[1101],{"type":46,"value":1102},"install-dynamic-plugins",{"type":46,"value":1104}," CLI and boots backend plugins via ",{"type":40,"tag":73,"props":1106,"children":1108},{"className":1107},[],[1109],{"type":46,"value":827},{"type":46,"value":1111},".",{"type":40,"tag":140,"props":1113,"children":1114},{},[1115,1119,1121,1126,1128,1133,1135,1141,1143,1149,1151,1157,1159,1165,1167,1173,1175,1181,1183,1189],{"type":40,"tag":55,"props":1116,"children":1117},{},[1118],{"type":46,"value":997},{"type":46,"value":1120}," write a ",{"type":40,"tag":73,"props":1122,"children":1124},{"className":1123},[],[1125],{"type":46,"value":449},{"type":46,"value":1127}," listing the ",{"type":40,"tag":73,"props":1129,"children":1131},{"className":1130},[],[1132],{"type":46,"value":457},{"type":46,"value":1134}," artifact refs to validate (a workspace's refs are the ",{"type":40,"tag":73,"props":1136,"children":1138},{"className":1137},[],[1139],{"type":46,"value":1140},"spec.dynamicArtifact",{"type":46,"value":1142}," values in ",{"type":40,"tag":73,"props":1144,"children":1146},{"className":1145},[],[1147],{"type":46,"value":1148},"overlays: workspaces\u002F\u003Cname>\u002Fmetadata\u002F*.yaml",{"type":46,"value":1150},"), then run ",{"type":40,"tag":73,"props":1152,"children":1154},{"className":1153},[],[1155],{"type":46,"value":1156},"yarn smoke --dynamic-plugins \u003Cfile> [--out results.json]",{"type":46,"value":1158},". Exit code 0 = pass; non-zero writes ",{"type":40,"tag":73,"props":1160,"children":1162},{"className":1161},[],[1163],{"type":46,"value":1164},"results.json",{"type":46,"value":1166}," detailing ",{"type":40,"tag":73,"props":1168,"children":1170},{"className":1169},[],[1171],{"type":46,"value":1172},"fail-load",{"type":46,"value":1174}," \u002F ",{"type":40,"tag":73,"props":1176,"children":1178},{"className":1177},[],[1179],{"type":46,"value":1180},"fail-start",{"type":46,"value":1182},". CI: ",{"type":40,"tag":73,"props":1184,"children":1186},{"className":1185},[],[1187],{"type":46,"value":1188},"overlays: .github\u002Fworkflows\u002Fnative-smoke.yaml",{"type":46,"value":1111},{"type":40,"tag":140,"props":1191,"children":1192},{},[1193,1198,1200,1205,1207,1213,1215,1221,1223,1229],{"type":40,"tag":55,"props":1194,"children":1195},{},[1196],{"type":46,"value":1197},"Known gaps (don't fight them):",{"type":46,"value":1199}," catalog-extending backend modules don't boot in a minimal ",{"type":40,"tag":73,"props":1201,"children":1203},{"className":1202},[],[1204],{"type":46,"value":827},{"type":46,"value":1206}," (upstream ",{"type":40,"tag":73,"props":1208,"children":1210},{"className":1209},[],[1211],{"type":46,"value":1212},"catalog-backend",{"type":46,"value":1214}," registration issue — stay on the Docker smoke until fixed); plugins whose ",{"type":40,"tag":73,"props":1216,"children":1218},{"className":1217},[],[1219],{"type":46,"value":1220},"dynamicArtifact",{"type":46,"value":1222}," is a local ",{"type":40,"tag":73,"props":1224,"children":1226},{"className":1225},[],[1227],{"type":46,"value":1228},".\u002Fdynamic-plugins\u002Fdist\u002F...",{"type":46,"value":1230}," path ship inside the RHDH image and have no OCI artifact to validate here.",{"type":40,"tag":140,"props":1232,"children":1233},{},[1234,1239,1241,1246],{"type":40,"tag":55,"props":1235,"children":1236},{},[1237],{"type":46,"value":1238},"Never add UI-render tests here",{"type":46,"value":1240}," — this repo has no app to render into. Delegate rendering to the ",{"type":40,"tag":73,"props":1242,"children":1244},{"className":1243},[],[1245],{"type":46,"value":110},{"type":46,"value":1247}," cluster-free harness.",{"type":40,"tag":140,"props":1249,"children":1250},{},[1251,1253,1258,1259,1264],{"type":46,"value":1252},"The Docker smoke it replaces (backend scope) lives at ",{"type":40,"tag":73,"props":1254,"children":1256},{"className":1255},[],[1257],{"type":46,"value":644},{"type":46,"value":646},{"type":40,"tag":73,"props":1260,"children":1262},{"className":1261},[],[1263],{"type":46,"value":652},{"type":46,"value":1111},{"type":40,"tag":140,"props":1266,"children":1267},{},[1268,1273,1275,1281,1283,1288],{"type":40,"tag":73,"props":1269,"children":1271},{"className":1270},[],[1272],{"type":46,"value":696},{"type":46,"value":1274}," (cluster, Playwright + ",{"type":40,"tag":73,"props":1276,"children":1278},{"className":1277},[],[1279],{"type":46,"value":1280},"e2e-test-utils",{"type":46,"value":1282},") exists for plugins whose value ",{"type":40,"tag":1060,"props":1284,"children":1285},{},[1286],{"type":46,"value":1287},"is",{"type":46,"value":1289}," cluster integration (topology, tekton, argocd…). Don't add one for UI-only behavior.",{"type":40,"tag":958,"props":1291,"children":1293},{"id":1292},"rhdh-the-real-app",[1294,1299],{"type":40,"tag":73,"props":1295,"children":1297},{"className":1296},[],[1298],{"type":46,"value":110},{"type":46,"value":1300}," — the real app",{"type":40,"tag":49,"props":1302,"children":1303},{},[1304,1306,1315],{"type":46,"value":1305},"Authoring reference for L1–L3: ",{"type":40,"tag":55,"props":1307,"children":1308},{},[1309],{"type":40,"tag":73,"props":1310,"children":1312},{"className":1311},[],[1313],{"type":46,"value":1314},"rhdh: docs\u002Ftesting.md",{"type":46,"value":1316}," — which utilities to import per layer, what to assert, and the current worked examples. It lives next to the code, so prefer it over any API name written here.",{"type":40,"tag":49,"props":1318,"children":1319},{},[1320],{"type":46,"value":1321},"Listed cheapest-first, matching the ladder in Step 3.",{"type":40,"tag":49,"props":1323,"children":1324},{},[1325,1330,1332,1338,1339,1345,1347,1353,1355,1361],{"type":40,"tag":55,"props":1326,"children":1327},{},[1328],{"type":46,"value":1329},"L1 unit",{"type":46,"value":1331}," (",{"type":40,"tag":73,"props":1333,"children":1335},{"className":1334},[],[1336],{"type":46,"value":1337},"rhdh: plugins\u002F*\u002Fsrc\u002F**",{"type":46,"value":80},{"type":40,"tag":73,"props":1340,"children":1342},{"className":1341},[],[1343],{"type":46,"value":1344},"rhdh: packages\u002Fapp\u002Fsrc\u002F**",{"type":46,"value":1346},") — pure logic, or a router you construct by hand and drive with ",{"type":40,"tag":73,"props":1348,"children":1350},{"className":1349},[],[1351],{"type":46,"value":1352},"supertest",{"type":46,"value":1354},"; no backend boot. Template to copy: ",{"type":40,"tag":73,"props":1356,"children":1358},{"className":1357},[],[1359],{"type":46,"value":1360},"rhdh: plugins\u002Fscalprum-backend\u002Fsrc\u002Fservice\u002Frouter.test.ts",{"type":46,"value":1362}," (table-driven, mock directory for on-disk plugin content).",{"type":40,"tag":49,"props":1364,"children":1365},{},[1366,1371,1372,1377,1379,1385,1387,1392,1394,1399,1401,1407,1409,1414],{"type":40,"tag":55,"props":1367,"children":1368},{},[1369],{"type":46,"value":1370},"L2 backend integration",{"type":46,"value":1331},{"type":40,"tag":73,"props":1373,"children":1375},{"className":1374},[],[1376],{"type":46,"value":1337},{"type":46,"value":1378},", named ",{"type":40,"tag":73,"props":1380,"children":1382},{"className":1381},[],[1383],{"type":46,"value":1384},"*.integration.test.ts",{"type":46,"value":1386},") — boots the ",{"type":40,"tag":55,"props":1388,"children":1389},{},[1390],{"type":46,"value":1391},"real plugin",{"type":46,"value":1393}," via ",{"type":40,"tag":73,"props":1395,"children":1397},{"className":1396},[],[1398],{"type":46,"value":827},{"type":46,"value":1400}," and asserts over HTTP. This is the layer that proves the plugin's wiring — routes, service dependencies, auth policy — which L1 cannot show. Template to copy: ",{"type":40,"tag":73,"props":1402,"children":1404},{"className":1403},[],[1405],{"type":46,"value":1406},"rhdh: plugins\u002Fscalprum-backend\u002Fsrc\u002Fservice\u002Frouter.integration.test.ts",{"type":46,"value":1408},". Assert the HTTP contract (status, body shape, what an unauthenticated caller gets), ",{"type":40,"tag":1060,"props":1410,"children":1411},{},[1412],{"type":46,"value":1413},"not",{"type":46,"value":1415}," that the plugin booted — a successful request already implies that. Booting is slow; allow a generous per-test timeout.",{"type":40,"tag":49,"props":1417,"children":1418},{},[1419,1424,1425,1431,1433,1440,1442,1448,1449,1455,1456,1462,1464,1470],{"type":40,"tag":55,"props":1420,"children":1421},{},[1422],{"type":46,"value":1423},"L3 component tests",{"type":46,"value":1331},{"type":40,"tag":73,"props":1426,"children":1428},{"className":1427},[],[1429],{"type":46,"value":1430},"rhdh: packages\u002Fapp\u002Fsrc\u002F**\u002F*.test.tsx",{"type":46,"value":1432},") — page-level RTL compositions, pattern established under RHIDP-13235 in ",{"type":40,"tag":656,"props":1434,"children":1437},{"href":1435,"rel":1436},"https:\u002F\u002Fgithub.com\u002Fredhat-developer\u002Frhdh\u002Fpull\u002F4864",[660],[1438],{"type":46,"value":1439},"rhdh#4864",{"type":46,"value":1441},". Existing templates to copy: ",{"type":40,"tag":73,"props":1443,"children":1445},{"className":1444},[],[1446],{"type":46,"value":1447},"InfoCard.test.tsx",{"type":46,"value":80},{"type":40,"tag":73,"props":1450,"children":1452},{"className":1451},[],[1453],{"type":46,"value":1454},"LearningPathsPage.test.tsx",{"type":46,"value":80},{"type":40,"tag":73,"props":1457,"children":1459},{"className":1458},[],[1460],{"type":46,"value":1461},"CustomSidebarItem.test.tsx",{"type":46,"value":1463}," (component compositions) and ",{"type":40,"tag":73,"props":1465,"children":1467},{"className":1466},[],[1468],{"type":46,"value":1469},"getMountPointData.test.ts",{"type":46,"value":1471}," (dynamic-UI helper). Prefer L3 over L4a when no dynamic-plugin loading is involved.",{"type":40,"tag":49,"props":1473,"children":1474},{},[1475,1480,1482,1487,1489,1495,1497,1502],{"type":40,"tag":55,"props":1476,"children":1477},{},[1478],{"type":46,"value":1479},"L4a cluster-free harness",{"type":46,"value":1481}," (config ",{"type":40,"tag":73,"props":1483,"children":1485},{"className":1484},[],[1486],{"type":46,"value":555},{"type":46,"value":1488},", docs ",{"type":40,"tag":73,"props":1490,"children":1492},{"className":1491},[],[1493],{"type":46,"value":1494},"rhdh: docs\u002Fe2e-tests\u002Flocal-e2e-harness.md",{"type":46,"value":1496},") — the only cheap place a frontend dynamic plugin can be ",{"type":40,"tag":1060,"props":1498,"children":1499},{},[1500],{"type":46,"value":1501},"rendered",{"type":46,"value":1503},". To enable a spec\u002Ftest:",{"type":40,"tag":167,"props":1505,"children":1506},{},[1507,1565,1586,1607],{"type":40,"tag":140,"props":1508,"children":1509},{},[1510,1512,1518,1520,1526,1528,1534,1536,1547,1549,1555,1557,1563],{"type":46,"value":1511},"Plugin not yet installed by the harness? Add its OCI entry to ",{"type":40,"tag":73,"props":1513,"children":1515},{"className":1514},[],[1516],{"type":46,"value":1517},"rhdh: e2e-tests\u002Flocal-harness\u002Fdynamic-plugins.yaml",{"type":46,"value":1519}," (tags on ",{"type":40,"tag":73,"props":1521,"children":1523},{"className":1522},[],[1524],{"type":46,"value":1525},"ghcr.io\u002Fredhat-developer\u002Frhdh-plugin-export-overlays\u002F\u003Cpackage>",{"type":46,"value":1527},"). If its mount-point config is not in the repo's static ",{"type":40,"tag":73,"props":1529,"children":1531},{"className":1530},[],[1532],{"type":46,"value":1533},"app-config.dynamic-plugins.yaml",{"type":46,"value":1535},", attach the plugin's ",{"type":40,"tag":55,"props":1537,"children":1538},{},[1539,1541],{"type":46,"value":1540},"canonical ",{"type":40,"tag":73,"props":1542,"children":1544},{"className":1543},[],[1545],{"type":46,"value":1546},"pluginConfig",{"type":46,"value":1548}," (source of truth: ",{"type":40,"tag":73,"props":1550,"children":1552},{"className":1551},[],[1553],{"type":46,"value":1554},"plugins: workspaces\u002F\u003Cws>\u002Fplugins\u002F\u003Cplugin>\u002Fapp-config.dynamic.yaml",{"type":46,"value":1556},") — the harness loads the generated ",{"type":40,"tag":73,"props":1558,"children":1560},{"className":1559},[],[1561],{"type":46,"value":1562},"dynamic-plugins-root\u002Fapp-config.dynamic-plugins.yaml",{"type":46,"value":1564}," last, exactly like the production container.",{"type":40,"tag":140,"props":1566,"children":1567},{},[1568,1570,1576,1578,1584],{"type":46,"value":1569},"Config that only exists in CI config maps (",{"type":40,"tag":73,"props":1571,"children":1573},{"className":1572},[],[1574],{"type":46,"value":1575},"rhdh: .ci\u002Fpipelines\u002Fresources\u002Fconfig_map\u002F*",{"type":46,"value":1577},")? Mirror just the needed keys into ",{"type":40,"tag":73,"props":1579,"children":1581},{"className":1580},[],[1582],{"type":46,"value":1583},"rhdh: app-config.local-e2e.yaml",{"type":46,"value":1585}," (objects deep-merge; arrays replace).",{"type":40,"tag":140,"props":1587,"children":1588},{},[1589,1591,1597,1599,1605],{"type":46,"value":1590},"Tag the test ",{"type":40,"tag":73,"props":1592,"children":1594},{"className":1593},[],[1595],{"type":46,"value":1596},"{ tag: \"@cluster-free\" }",{"type":46,"value":1598}," and add its spec file to the config's ",{"type":40,"tag":73,"props":1600,"children":1602},{"className":1601},[],[1603],{"type":46,"value":1604},"testMatch",{"type":46,"value":1606}," allowlist.",{"type":40,"tag":140,"props":1608,"children":1609},{},[1610,1612,1618,1620,1626,1628,1634],{"type":46,"value":1611},"Repopulate + validate: ",{"type":40,"tag":73,"props":1613,"children":1615},{"className":1614},[],[1616],{"type":46,"value":1617},".\u002Fe2e-tests\u002Flocal-harness\u002Fpopulate.sh",{"type":46,"value":1619},", then ",{"type":40,"tag":73,"props":1621,"children":1623},{"className":1622},[],[1624],{"type":46,"value":1625},"yarn --cwd e2e-tests e2e:legacy-local",{"type":46,"value":1627}," (CI runs this in ~4 min). Requires skopeo — preinstalled in CI; on macOS ",{"type":40,"tag":73,"props":1629,"children":1631},{"className":1630},[],[1632],{"type":46,"value":1633},"brew install skopeo",{"type":46,"value":1111},{"type":40,"tag":49,"props":1636,"children":1637},{},[1638,1643,1644,1650,1652,1656,1658,1664,1666,1672,1674,1680,1682,1688],{"type":40,"tag":55,"props":1639,"children":1640},{},[1641],{"type":46,"value":1642},"L4b cluster e2e",{"type":46,"value":1331},{"type":40,"tag":73,"props":1645,"children":1647},{"className":1646},[],[1648],{"type":46,"value":1649},"rhdh: e2e-tests\u002Fplaywright\u002Fe2e\u002F**",{"type":46,"value":1651},") — only when the subject ",{"type":40,"tag":1060,"props":1653,"children":1654},{},[1655],{"type":46,"value":1287},{"type":46,"value":1657}," cluster\u002Fplatform behavior or a real external service. Requirements: ",{"type":40,"tag":73,"props":1659,"children":1661},{"className":1660},[],[1662],{"type":46,"value":1663},"component",{"type":46,"value":1665}," annotation in ",{"type":40,"tag":73,"props":1667,"children":1669},{"className":1668},[],[1670],{"type":46,"value":1671},"beforeAll",{"type":46,"value":1673}," (see the repo's ",{"type":40,"tag":73,"props":1675,"children":1677},{"className":1676},[],[1678],{"type":46,"value":1679},"ci-e2e-testing",{"type":46,"value":1681}," rule), correct config map choice (RBAC vs non-RBAC), project registration in ",{"type":40,"tag":73,"props":1683,"children":1685},{"className":1684},[],[1686],{"type":46,"value":1687},"e2e-tests\u002Fplaywright\u002Fprojects.json",{"type":46,"value":1689}," if a new project is needed.",{"type":40,"tag":49,"props":1691,"children":1692},{},[1693,1698],{"type":40,"tag":55,"props":1694,"children":1695},{},[1696],{"type":46,"value":1697},"Catalog-index-wide sanity",{"type":46,"value":1699}," (nightly, in review — see References) — nothing to write per plugin; it sweeps the whole index.",{"type":40,"tag":129,"props":1701,"children":1703},{"id":1702},"step-5-write-the-test",[1704],{"type":46,"value":1705},"Step 5 — Write the test",{"type":40,"tag":49,"props":1707,"children":1708},{},[1709,1711,1716,1718,1724,1726,1732,1734,1739],{"type":46,"value":1710},"Once Steps 2–4 have settled the repo and the layer, write it by ",{"type":40,"tag":55,"props":1712,"children":1713},{},[1714],{"type":46,"value":1715},"mirroring a real file",{"type":46,"value":1717},", never from memory of an API. The Backstage test surface moves (",{"type":40,"tag":73,"props":1719,"children":1721},{"className":1720},[],[1722],{"type":46,"value":1723},"@backstage\u002Ftest-utils",{"type":46,"value":1725}," was renamed to ",{"type":40,"tag":73,"props":1727,"children":1729},{"className":1728},[],[1730],{"type":46,"value":1731},"@backstage\u002Ffrontend-test-utils",{"type":46,"value":1733},"), so a package or helper name recalled from training data is likely to be stale — a file on ",{"type":40,"tag":73,"props":1735,"children":1737},{"className":1736},[],[1738],{"type":46,"value":125},{"type":46,"value":1740}," cannot be.",{"type":40,"tag":167,"props":1742,"children":1743},{},[1744,1761,1785,1810],{"type":40,"tag":140,"props":1745,"children":1746},{},[1747,1752,1754,1759],{"type":40,"tag":55,"props":1748,"children":1749},{},[1750],{"type":46,"value":1751},"Open the template",{"type":46,"value":1753}," named in Step 4 for the chosen layer and read it in full. If the path no longer exists, glob its directory for a sibling ",{"type":40,"tag":73,"props":1755,"children":1757},{"className":1756},[],[1758],{"type":46,"value":1005},{"type":46,"value":1760}," and use that; if the directory is gone too, say so rather than inventing imports.",{"type":40,"tag":140,"props":1762,"children":1763},{},[1764,1769,1771,1776,1778,1783],{"type":40,"tag":55,"props":1765,"children":1766},{},[1767],{"type":46,"value":1768},"Consult the repo's own authoring reference",{"type":46,"value":1770}," — for ",{"type":40,"tag":73,"props":1772,"children":1774},{"className":1773},[],[1775],{"type":46,"value":110},{"type":46,"value":1777}," L1–L3 that is ",{"type":40,"tag":73,"props":1779,"children":1781},{"className":1780},[],[1782],{"type":46,"value":1314},{"type":46,"value":1784},", which lists the utilities per layer and what to assert. It is maintained next to the code, so it wins over anything written in this skill.",{"type":40,"tag":140,"props":1786,"children":1787},{},[1788,1793,1795,1801,1803,1808],{"type":40,"tag":55,"props":1789,"children":1790},{},[1791],{"type":46,"value":1792},"Mirror, then adapt",{"type":46,"value":1794}," — copy the template's imports, setup and teardown; replace the subject and the assertions. Keep the file next to the code, matching the neighbours' naming (",{"type":40,"tag":73,"props":1796,"children":1798},{"className":1797},[],[1799],{"type":46,"value":1800},"*.test.ts",{"type":46,"value":1802}," for L1, ",{"type":40,"tag":73,"props":1804,"children":1806},{"className":1805},[],[1807],{"type":46,"value":1384},{"type":46,"value":1809}," for L2).",{"type":40,"tag":140,"props":1811,"children":1812},{},[1813,1818],{"type":40,"tag":55,"props":1814,"children":1815},{},[1816],{"type":46,"value":1817},"Run it",{"type":46,"value":1819}," before reporting done:",{"type":40,"tag":250,"props":1821,"children":1822},{},[1823,1839],{"type":40,"tag":254,"props":1824,"children":1825},{},[1826],{"type":40,"tag":258,"props":1827,"children":1828},{},[1829,1834],{"type":40,"tag":262,"props":1830,"children":1831},{},[1832],{"type":46,"value":1833},"Placement",{"type":40,"tag":262,"props":1835,"children":1836},{},[1837],{"type":46,"value":1838},"Command",{"type":40,"tag":288,"props":1840,"children":1841},{},[1842,1864,1892,1914,1935],{"type":40,"tag":258,"props":1843,"children":1844},{},[1845,1855],{"type":40,"tag":295,"props":1846,"children":1847},{},[1848,1853],{"type":40,"tag":73,"props":1849,"children":1851},{"className":1850},[],[1852],{"type":46,"value":110},{"type":46,"value":1854}," L1 \u002F L2 \u002F L3",{"type":40,"tag":295,"props":1856,"children":1857},{},[1858],{"type":40,"tag":73,"props":1859,"children":1861},{"className":1860},[],[1862],{"type":46,"value":1863},"yarn test --filter=\u003Cpackage>",{"type":40,"tag":258,"props":1865,"children":1866},{},[1867,1877],{"type":40,"tag":295,"props":1868,"children":1869},{},[1870,1875],{"type":40,"tag":73,"props":1871,"children":1873},{"className":1872},[],[1874],{"type":46,"value":110},{"type":46,"value":1876}," L4a",{"type":40,"tag":295,"props":1878,"children":1879},{},[1880,1885,1887],{"type":40,"tag":73,"props":1881,"children":1883},{"className":1882},[],[1884],{"type":46,"value":1617},{"type":46,"value":1886}," then ",{"type":40,"tag":73,"props":1888,"children":1890},{"className":1889},[],[1891],{"type":46,"value":1625},{"type":40,"tag":258,"props":1893,"children":1894},{},[1895,1905],{"type":40,"tag":295,"props":1896,"children":1897},{},[1898,1903],{"type":40,"tag":73,"props":1899,"children":1901},{"className":1900},[],[1902],{"type":46,"value":110},{"type":46,"value":1904}," L4b",{"type":40,"tag":295,"props":1906,"children":1907},{},[1908],{"type":40,"tag":73,"props":1909,"children":1911},{"className":1910},[],[1912],{"type":46,"value":1913},"yarn --cwd e2e-tests playwright test --project=\u003Cproject>",{"type":40,"tag":258,"props":1915,"children":1916},{},[1917,1925],{"type":40,"tag":295,"props":1918,"children":1919},{},[1920],{"type":40,"tag":73,"props":1921,"children":1923},{"className":1922},[],[1924],{"type":46,"value":195},{"type":40,"tag":295,"props":1926,"children":1927},{},[1928,1930],{"type":46,"value":1929},"the workspace's ",{"type":40,"tag":73,"props":1931,"children":1933},{"className":1932},[],[1934],{"type":46,"value":1013},{"type":40,"tag":258,"props":1936,"children":1937},{},[1938,1949],{"type":40,"tag":295,"props":1939,"children":1940},{},[1941,1947],{"type":40,"tag":73,"props":1942,"children":1944},{"className":1943},[],[1945],{"type":46,"value":1946},"overlays",{"type":46,"value":1948}," native smoke",{"type":40,"tag":295,"props":1950,"children":1951},{},[1952],{"type":40,"tag":73,"props":1953,"children":1955},{"className":1954},[],[1956],{"type":46,"value":1957},"yarn smoke --dynamic-plugins \u003Cfile>",{"type":40,"tag":167,"props":1959,"children":1961},{"start":1960},5,[1962],{"type":40,"tag":140,"props":1963,"children":1964},{},[1965,1970,1972,1977,1979,1984,1986,1991,1992,1997,1999,2005],{"type":40,"tag":55,"props":1966,"children":1967},{},[1968],{"type":46,"value":1969},"Layer checklist",{"type":46,"value":1971}," — L2: assert the HTTP contract, not that the plugin booted; allow a generous timeout. L4a: tag ",{"type":40,"tag":73,"props":1973,"children":1975},{"className":1974},[],[1976],{"type":46,"value":1596},{"type":46,"value":1978}," and add the spec to the config's ",{"type":40,"tag":73,"props":1980,"children":1982},{"className":1981},[],[1983],{"type":46,"value":1604},{"type":46,"value":1985}," allowlist. L4b: ",{"type":40,"tag":73,"props":1987,"children":1989},{"className":1988},[],[1990],{"type":46,"value":1663},{"type":46,"value":1665},{"type":40,"tag":73,"props":1993,"children":1995},{"className":1994},[],[1996],{"type":46,"value":1671},{"type":46,"value":1998},", correct RBAC vs non-RBAC config map, and register the project in ",{"type":40,"tag":73,"props":2000,"children":2002},{"className":2001},[],[2003],{"type":46,"value":2004},"projects.json",{"type":46,"value":2006}," if new.",{"type":40,"tag":49,"props":2008,"children":2009},{},[2010,2015,2017,2023,2025,2030,2032,2037],{"type":40,"tag":55,"props":2011,"children":2012},{},[2013],{"type":46,"value":2014},"Justify the test by the failure it would catch, not by coverage.",{"type":46,"value":2016}," Codecov is ",{"type":40,"tag":73,"props":2018,"children":2020},{"className":2019},[],[2021],{"type":46,"value":2022},"informational: true",{"type":46,"value":2024}," on both the project and patch statuses in ",{"type":40,"tag":73,"props":2026,"children":2028},{"className":2027},[],[2029],{"type":46,"value":110},{"type":46,"value":2031}," and ",{"type":40,"tag":73,"props":2033,"children":2035},{"className":2034},[],[2036],{"type":46,"value":195},{"type":46,"value":2038},", so no coverage number can block a PR and there is no threshold to reach. A test whose only rationale is a coverage delta will be — and has been — rejected in review.",{"type":40,"tag":129,"props":2040,"children":2042},{"id":2041},"not-possible-today-researched-dont-burn-time",[2043],{"type":46,"value":2044},"Not possible today (researched — don't burn time)",{"type":40,"tag":136,"props":2046,"children":2047},{},[2048,2058,2074],{"type":40,"tag":140,"props":2049,"children":2050},{},[2051,2056],{"type":40,"tag":55,"props":2052,"children":2053},{},[2054],{"type":46,"value":2055},"Rendering a frontend dynamic plugin without an RHDH app",{"type":46,"value":2057}," — the artifact is a legacy-frontend Scalprum bundle; no standalone host exists, and building one means maintaining a version-coupled \"mini RHDH\".",{"type":40,"tag":140,"props":2059,"children":2060},{},[2061,2072],{"type":40,"tag":55,"props":2062,"children":2063},{},[2064,2070],{"type":40,"tag":73,"props":2065,"children":2067},{"className":2066},[],[2068],{"type":46,"value":2069},"@backstage\u002Ffrontend-dynamic-feature-loader",{"type":46,"value":2071}," for current plugins",{"type":46,"value":2073}," — targets the new frontend system (alpha); our exported plugins are legacy-system bundles. Revisit when app-next matures (RHIDP-15082).",{"type":40,"tag":140,"props":2075,"children":2076},{},[2077,2087,2089,2094],{"type":40,"tag":55,"props":2078,"children":2079},{},[2080,2082],{"type":46,"value":2081},"Catalog-extending modules in a minimal ",{"type":40,"tag":73,"props":2083,"children":2085},{"className":2084},[],[2086],{"type":46,"value":827},{"type":46,"value":2088}," — upstream ",{"type":40,"tag":73,"props":2090,"children":2092},{"className":2091},[],[2093],{"type":46,"value":1212},{"type":46,"value":2095}," issue (see overlays harness notes).",{"type":40,"tag":129,"props":2097,"children":2099},{"id":2098},"output-format",[2100],{"type":46,"value":2101},"Output format",{"type":40,"tag":49,"props":2103,"children":2104},{},[2105],{"type":46,"value":2106},"Answer with a concrete recommendation:",{"type":40,"tag":136,"props":2108,"children":2109},{},[2110,2120,2130,2140,2150,2160],{"type":40,"tag":140,"props":2111,"children":2112},{},[2113,2118],{"type":40,"tag":55,"props":2114,"children":2115},{},[2116],{"type":46,"value":2117},"Repo:",{"type":46,"value":2119}," which of the three (or the plugin's own source repo).",{"type":40,"tag":140,"props":2121,"children":2122},{},[2123,2128],{"type":40,"tag":55,"props":2124,"children":2125},{},[2126],{"type":46,"value":2127},"Layer \u002F harness:",{"type":46,"value":2129}," L1–L4b + the specific harness or suite.",{"type":40,"tag":140,"props":2131,"children":2132},{},[2133,2138],{"type":40,"tag":55,"props":2134,"children":2135},{},[2136],{"type":46,"value":2137},"Location:",{"type":46,"value":2139}," the directory\u002Ffile where the test goes.",{"type":40,"tag":140,"props":2141,"children":2142},{},[2143,2148],{"type":40,"tag":55,"props":2144,"children":2145},{},[2146],{"type":46,"value":2147},"Scaffolding:",{"type":46,"value":2149}," the minimal steps or files to create, referencing an existing neighbor as template.",{"type":40,"tag":140,"props":2151,"children":2152},{},[2153,2158],{"type":40,"tag":55,"props":2154,"children":2155},{},[2156],{"type":46,"value":2157},"Why not elsewhere:",{"type":46,"value":2159}," one line on the layers you rejected (especially if the dev proposed a more expensive one).",{"type":40,"tag":140,"props":2161,"children":2162},{},[2163,2168],{"type":40,"tag":55,"props":2164,"children":2165},{},[2166],{"type":46,"value":2167},"Cost:",{"type":46,"value":2169}," rough feedback time (seconds \u002F ~4 min cluster-free \u002F cluster job).",{"type":40,"tag":129,"props":2171,"children":2173},{"id":2172},"references-pr-statuses-are-as-of-the-last-edit-verify-before-citing-as-merged",[2174],{"type":46,"value":2175},"References (PR statuses are as of the last edit — verify before citing as merged)",{"type":40,"tag":136,"props":2177,"children":2178},{},[2179,2199,2239,2264,2287,2309,2322],{"type":40,"tag":140,"props":2180,"children":2181},{},[2182,2184,2189,2191,2197],{"type":46,"value":2183},"Epic ",{"type":40,"tag":55,"props":2185,"children":2186},{},[2187],{"type":46,"value":2188},"RHIDP-13501",{"type":46,"value":2190}," (E2E Test Optimization) — the per-repo responsibility split lives in the epic's comments and its Jira attachment ",{"type":40,"tag":73,"props":2192,"children":2194},{"className":2193},[],[2195],{"type":46,"value":2196},"rhdh-dynamic-plugin-testing-guideline.md",{"type":46,"value":2198}," (Jira-only; if unreachable, the decision table above is the summary).",{"type":40,"tag":140,"props":2200,"children":2201},{},[2202,2204,2214,2216,2223,2225,2230,2232,2237],{"type":46,"value":2203},"Layer matrix: ",{"type":40,"tag":656,"props":2205,"children":2208},{"href":2206,"rel":2207},"https:\u002F\u002Fgithub.com\u002Fredhat-developer\u002Frhdh\u002Fblob\u002Fmain\u002Fdocs\u002Fe2e-tests\u002Flayer-migration-matrix.md",[660],[2209],{"type":40,"tag":73,"props":2210,"children":2212},{"className":2211},[],[2213],{"type":46,"value":948},{"type":46,"value":2215}," — merged in ",{"type":40,"tag":656,"props":2217,"children":2220},{"href":2218,"rel":2219},"https:\u002F\u002Fgithub.com\u002Fredhat-developer\u002Frhdh\u002Fpull\u002F5044",[660],[2221],{"type":46,"value":2222},"rhdh#5044",{"type":46,"value":2224}," (RHIDP-15076). This is a ",{"type":40,"tag":1060,"props":2226,"children":2227},{},[2228],{"type":46,"value":2229},"migration",{"type":46,"value":2231}," analysis of the existing e2e suite; for ",{"type":40,"tag":55,"props":2233,"children":2234},{},[2235],{"type":46,"value":2236},"how to write",{"type":46,"value":2238}," an L1–L3 test see the next entry.",{"type":40,"tag":140,"props":2240,"children":2241},{},[2242,2244,2254,2255,2262],{"type":46,"value":2243},"L1–L3 authoring guide: ",{"type":40,"tag":656,"props":2245,"children":2248},{"href":2246,"rel":2247},"https:\u002F\u002Fgithub.com\u002Fredhat-developer\u002Frhdh\u002Fblob\u002Fmain\u002Fdocs\u002Ftesting.md",[660],[2249],{"type":40,"tag":73,"props":2250,"children":2252},{"className":2251},[],[2253],{"type":46,"value":1314},{"type":46,"value":2215},{"type":40,"tag":656,"props":2256,"children":2259},{"href":2257,"rel":2258},"https:\u002F\u002Fgithub.com\u002Fredhat-developer\u002Frhdh\u002Fpull\u002F5140",[660],[2260],{"type":46,"value":2261},"rhdh#5140",{"type":46,"value":2263}," (RHIDP-13234). Utilities per layer, what to assert, and the worked examples Step 4 points at.",{"type":40,"tag":140,"props":2265,"children":2266},{},[2267,2269,2276,2278,2285],{"type":46,"value":2268},"Cluster-free harness + docs: merged in ",{"type":40,"tag":656,"props":2270,"children":2273},{"href":2271,"rel":2272},"https:\u002F\u002Fgithub.com\u002Fredhat-developer\u002Frhdh\u002Fpull\u002F5005",[660],[2274],{"type":46,"value":2275},"rhdh#5005",{"type":46,"value":2277}," (RHIDP-15075); expanded to 10 specs \u002F 14 test cases in ",{"type":40,"tag":656,"props":2279,"children":2282},{"href":2280,"rel":2281},"https:\u002F\u002Fgithub.com\u002Fredhat-developer\u002Frhdh\u002Fpull\u002F5057",[660],[2283],{"type":46,"value":2284},"rhdh#5057",{"type":46,"value":2286}," — the richest set of worked enablement examples (config mirrors, catalog file locations, OCI plugin additions).",{"type":40,"tag":140,"props":2288,"children":2289},{},[2290,2292,2299,2301,2308],{"type":46,"value":2291},"Overlays native smoke: merged in ",{"type":40,"tag":656,"props":2293,"children":2296},{"href":2294,"rel":2295},"https:\u002F\u002Fgithub.com\u002Fredhat-developer\u002Frhdh-plugin-export-overlays\u002Fpull\u002F2714",[660],[2297],{"type":46,"value":2298},"overlays#2714",{"type":46,"value":2300},"; the per-workspace mode used in Step 4 merged in ",{"type":40,"tag":656,"props":2302,"children":2305},{"href":2303,"rel":2304},"https:\u002F\u002Fgithub.com\u002Fredhat-developer\u002Frhdh-plugin-export-overlays\u002Fpull\u002F2731",[660],[2306],{"type":46,"value":2307},"overlays#2731",{"type":46,"value":1111},{"type":40,"tag":140,"props":2310,"children":2311},{},[2312,2314,2321],{"type":46,"value":2313},"Catalog-index sanity check: in review in ",{"type":40,"tag":656,"props":2315,"children":2318},{"href":2316,"rel":2317},"https:\u002F\u002Fgithub.com\u002Fredhat-developer\u002Frhdh\u002Fpull\u002F4967",[660],[2319],{"type":46,"value":2320},"rhdh#4967",{"type":46,"value":1111},{"type":40,"tag":140,"props":2323,"children":2324},{},[2325,2327,2332],{"type":46,"value":2326},"L3 pattern: merged in ",{"type":40,"tag":656,"props":2328,"children":2330},{"href":1435,"rel":2329},[660],[2331],{"type":46,"value":1439},{"type":46,"value":2333}," under RHIDP-13235.",{"items":2335,"total":2503},[2336,2350,2363,2382,2396,2413,2424,2443,2455,2466,2478,2490],{"slug":2337,"name":2337,"fn":2338,"description":2339,"org":2340,"tags":2341,"stars":23,"repoUrl":24,"updatedAt":2349},"agent-ready","assess repository readiness for AI agents","Assesses a git repository's readiness for use by AI coding agents using the agentready CLI, then walks through and addresses each gap. RHDH-aware: detects RHDH repositories and uses rhdh-repos.md context to pre-fill AGENTS.md and skip inapplicable findings. Use when asked to \"assess agent readiness\", \"run agentready\", \"check how agent-ready this repo is\", \"make this repo agent-ready\", \"improve agent readiness score\", \"assess all RHDH repos\", \"batch agent readiness\", or \"onboard this repo for agents\".\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2342,2345,2348],{"name":2343,"slug":2344,"type":16},"Agents","agents",{"name":2346,"slug":2347,"type":16},"Code Analysis","code-analysis",{"name":14,"slug":15,"type":16},"2026-07-16T06:03:24.758348",{"slug":2351,"name":2351,"fn":2352,"description":2353,"org":2354,"tags":2355,"stars":23,"repoUrl":24,"updatedAt":2362},"backstage-upgrade","upgrade Backstage dependencies","Upgrade @backstage\u002F* dependencies in a plugin or app to a target version. Use when asked to \"upgrade backstage\", \"bump backstage\", \"update @backstage\", \"align backstage deps\", \"backstage version bump\", \"upgrade dependencies\", \"backstage-cli versions:bump\", \"update to latest backstage\", \"fix version mismatch\", \"backstage version alignment\", \"upgrade before migration\", or any request to update Backstage package versions in a project.\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2356,2357,2360],{"name":14,"slug":15,"type":16},{"name":2358,"slug":2359,"type":16},"Maintenance","maintenance",{"name":2361,"slug":2229,"type":16},"Migration","2026-07-16T06:03:24.067361",{"slug":2364,"name":2364,"fn":2365,"description":2366,"org":2367,"tags":2368,"stars":23,"repoUrl":24,"updatedAt":2381},"base-images-and-rpms","update base images and lockfiles","Updates base images with updateBaseImages.sh and regenerates rpms.lock.yaml with rpm-lockfile-prototype in redhat-developer\u002Frhdh, rhdh-must-gather, and rhdh-operator. Use --analyze for read-only Containerfile\u002FDockerfile scan (current vs latest tags, UBI skew). Use for weekly upstream maintenance, UBI\u002FRHEL base image bumps, RPM lockfile refresh, base-images-and-rpms, main, release-*, or analyzing base images before updating.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2369,2372,2375,2378],{"name":2370,"slug":2371,"type":16},"Automation","automation",{"name":2373,"slug":2374,"type":16},"Configuration","configuration",{"name":2376,"slug":2377,"type":16},"Containers","containers",{"name":2379,"slug":2380,"type":16},"Deployment","deployment","2026-07-16T06:03:27.784453",{"slug":2383,"name":2383,"fn":2384,"description":2385,"org":2386,"tags":2387,"stars":23,"repoUrl":24,"updatedAt":2395},"bug-fix","diagnose and fix plugin bugs","Reproduce, diagnose, fix, and PR plugin bugs from Jira tickets or GitHub issues. Works with both rhdh-plugins and community-plugins. Supports UI bugs (Playwright e2e with before\u002Fafter recordings) and backend bugs (unit\u002Fintegration test verification). Triages issues for agent-readiness before attempting a fix. Accepts a Jira key (RHDHBUGS-1934), Jira URL (redhat.atlassian.net\u002Fbrowse\u002F...), GitHub issue URL (github.com\u002F...\u002Fissues\u002FN), or a request to \"fix this bug\", \"reproduce and fix\", \"\u002Fbug-fix\". By default, stops after the fix for user verification before PR creation. Supports --no-verify mode to skip the verification gate and auto-create the PR. Chains into raise-pr for the full PR lifecycle.\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2388,2391,2394],{"name":2389,"slug":2390,"type":16},"Debugging","debugging",{"name":2392,"slug":2393,"type":16},"QA","qa",{"name":21,"slug":22,"type":16},"2026-07-29T06:00:33.054762",{"slug":2397,"name":2397,"fn":2398,"description":2399,"org":2400,"tags":2401,"stars":23,"repoUrl":24,"updatedAt":2412},"compute-plugin-package-overlay-cve-list","generate CVE management reports","Builds an RHDH CVE Management CSV from GA plugin workspace changes in rhdh-plugin-export-overlays since a version tag (patches, source.json, plugins-list). One row per CVE × plugins-list package (multi-workspace CVEs expand). Use for \"CVE CSV\", \"workspace CVEs since tag\", \"collect overlay CVEs\", \"CVE management CSV\", orchestrator\u002Flightspeed CVE review, or plugin package overlay CVE list.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2402,2405,2406,2409],{"name":2403,"slug":2404,"type":16},"Compliance","compliance",{"name":14,"slug":15,"type":16},{"name":2407,"slug":2408,"type":16},"Reporting","reporting",{"name":2410,"slug":2411,"type":16},"Security","security","2026-08-01T05:41:57.852801",{"slug":2414,"name":2414,"fn":2415,"description":2416,"org":2417,"tags":2418,"stars":23,"repoUrl":24,"updatedAt":2423},"create-plugin","scaffold and package RHDH dynamic plugins","Full lifecycle for RHDH dynamic plugins — scaffold, implement, export, package, and configure. Use when asked to \"create RHDH plugin\", \"bootstrap dynamic plugin\", \"create backend plugin\", \"create frontend plugin\", \"export dynamic plugin\", \"package plugin as OCI\", \"generate frontend wiring\", \"create plugin container image\", \"configure mount points\", \"create dynamic route\", \"add entity card\", \"scaffold RHDH plugin\", \"publish plugin to registry\", \"create tgz archive\", or mentions creating, exporting, packaging, or wiring a Backstage plugin for Red Hat Developer Hub. Also use when asked to \"build a plugin from scratch\", \"dynamic plugin tutorial\", \"RHDH plugin from scratch\", or \"build Backstage plugin for RHDH\". Covers backend plugins (APIs, scaffolder actions, processors), frontend plugins (pages, cards, themes), export\u002Fpackaging (OCI, tgz, npm), and frontend wiring configuration (mount points, routes, entity tabs, themes).\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2419,2420],{"name":2379,"slug":2380,"type":16},{"name":2421,"slug":2422,"type":16},"Plugin Development","plugin-development","2026-07-16T06:02:28.983498",{"slug":2425,"name":2425,"fn":2426,"description":2427,"org":2428,"tags":2429,"stars":23,"repoUrl":24,"updatedAt":2442},"cursor-mcp-auth","authenticate Atlassian MCP for Jira","Ensures Cursor Atlassian MCP (plugin-atlassian-atlassian → mcp.atlassian.com) is authenticated for redhat.atlassian.net via OAuth. Use for \"authenticate Jira\", \"Atlassian MCP\", \"jira auth failed\", \"mcp_auth Atlassian\", \"cursor MCP auth\", or when another skill needs working Jira access through Cursor MCP.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2430,2433,2436,2439],{"name":2431,"slug":2432,"type":16},"Auth","auth",{"name":2434,"slug":2435,"type":16},"Jira","jira",{"name":2437,"slug":2438,"type":16},"MCP","mcp",{"name":2440,"slug":2441,"type":16},"OAuth","oauth","2026-07-29T06:00:33.525179",{"slug":2444,"name":2444,"fn":2445,"description":2446,"org":2447,"tags":2448,"stars":23,"repoUrl":24,"updatedAt":2454},"konflux-release-data-rpa","update release tags in konflux-release-data","Bumps RHDH ReleasePlanAdmission tag versions in konflux-release-data for a stream release (e.g. 1.9.7), opens a GitLab merge request, and launches it in the browser. Use when updating RPA tags, konflux-release-data, ReleasePlanAdmission, rhdh-1-9-*.yaml, rhdh-plugin-catalog-1-9-*.yaml, or preparing an RHDH patch release.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2449,2450,2451],{"name":2370,"slug":2371,"type":16},{"name":2379,"slug":2380,"type":16},{"name":2452,"slug":2453,"type":16},"GitLab","gitlab","2026-07-16T06:00:19.516231",{"slug":2456,"name":2456,"fn":2457,"description":2458,"org":2459,"tags":2460,"stars":23,"repoUrl":24,"updatedAt":2465},"konflux-tekton-updates","update Konflux Tekton task digests","Bumps Konflux Tekton task digests with .tekton\u002FupdateDigests.sh --minor --no-push, applies konflux-ci\u002Fbuild-definitions MIGRATION.md pipeline fixes, and regenerates PipelineRuns. Use for rhdh-plugin-catalog, RHDH midstream (4-rhdh), Konflux task minor bumps, prefetch-dependencies-oci-ta, build-image-index, or updateDigests.sh.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2461,2462,2463,2464],{"name":2370,"slug":2371,"type":16},{"name":18,"slug":19,"type":16},{"name":2379,"slug":2380,"type":16},{"name":14,"slug":15,"type":16},"2026-07-16T06:03:28.123118",{"slug":2467,"name":2467,"fn":2468,"description":2469,"org":2470,"tags":2471,"stars":23,"repoUrl":24,"updatedAt":2477},"lifecycle","check Red Hat product lifecycle status","Check version lifecycle and support status for platforms and integrations used by RHDH. Covers OCP, AKS, EKS, GKE, RHDH releases, RHBK, Quay, PostgreSQL, and any Red Hat product via the Product Life Cycles API. Use when asking about version support, EOL dates, GA dates, support phases, or planning version upgrades. Also use for \"is X still supported\", \"what versions should we test\", or \"when does X reach EOL\".",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2472,2473,2474],{"name":2403,"slug":2404,"type":16},{"name":2358,"slug":2359,"type":16},{"name":2475,"slug":2476,"type":16},"Operations","operations","2026-07-16T06:03:19.344038",{"slug":2479,"name":2479,"fn":2480,"description":2481,"org":2482,"tags":2483,"stars":23,"repoUrl":24,"updatedAt":2489},"nfs-migration","migrate plugins to New Frontend System","Migrate Backstage frontend plugins from the legacy system to the New Frontend System (NFS). Use when asked to \"migrate to NFS\", \"new frontend system\", \"convert plugin to NFS\", \"createFrontendPlugin\", \"PageBlueprint\", \"ApiBlueprint\", \"SubPageBlueprint\", \"alpha to GA\", \"legacy to NFS\", \"frontend migration\", \"extension blueprints\", \"migrate frontend plugin\", \"NFS support\", \"graduate alpha\", or mentions migrating a Backstage plugin to the new frontend system for RHDH.\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2484,2485,2488],{"name":14,"slug":15,"type":16},{"name":2486,"slug":2487,"type":16},"Frontend","frontend",{"name":2361,"slug":2229,"type":16},"2026-07-16T06:03:27.078987",{"slug":2491,"name":2491,"fn":2492,"description":2493,"org":2494,"tags":2495,"stars":23,"repoUrl":24,"updatedAt":2502},"overlay","manage RHDH plugin export overlays","Manages the rhdh-plugin-export-overlays repository — onboards plugins to the Extensions Catalog, updates plugin versions, fixes overlay build failures, triages and analyzes PRs, triggers publishes, and manages plugin workspaces. Use when working with overlays, importing plugins, debugging CI, checking PRs, bumping versions, or mentions \"Extensions Catalog\", \"overlay build failed\", \"plugin registry\", \"overlay PR\", \"overlay doctor\", \"plugin import\", \"add plugin to catalog\", \"onboard plugin\", or \"plugin workspace\".",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2496,2497,2500,2501],{"name":2370,"slug":2371,"type":16},{"name":2498,"slug":2499,"type":16},"Code Review","code-review",{"name":2379,"slug":2380,"type":16},{"name":14,"slug":15,"type":16},"2026-07-16T06:03:20.380044",24,{"items":2505,"total":2503},[2506,2512,2518,2525,2531,2538,2543],{"slug":2337,"name":2337,"fn":2338,"description":2339,"org":2507,"tags":2508,"stars":23,"repoUrl":24,"updatedAt":2349},{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2509,2510,2511],{"name":2343,"slug":2344,"type":16},{"name":2346,"slug":2347,"type":16},{"name":14,"slug":15,"type":16},{"slug":2351,"name":2351,"fn":2352,"description":2353,"org":2513,"tags":2514,"stars":23,"repoUrl":24,"updatedAt":2362},{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2515,2516,2517],{"name":14,"slug":15,"type":16},{"name":2358,"slug":2359,"type":16},{"name":2361,"slug":2229,"type":16},{"slug":2364,"name":2364,"fn":2365,"description":2366,"org":2519,"tags":2520,"stars":23,"repoUrl":24,"updatedAt":2381},{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2521,2522,2523,2524],{"name":2370,"slug":2371,"type":16},{"name":2373,"slug":2374,"type":16},{"name":2376,"slug":2377,"type":16},{"name":2379,"slug":2380,"type":16},{"slug":2383,"name":2383,"fn":2384,"description":2385,"org":2526,"tags":2527,"stars":23,"repoUrl":24,"updatedAt":2395},{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2528,2529,2530],{"name":2389,"slug":2390,"type":16},{"name":2392,"slug":2393,"type":16},{"name":21,"slug":22,"type":16},{"slug":2397,"name":2397,"fn":2398,"description":2399,"org":2532,"tags":2533,"stars":23,"repoUrl":24,"updatedAt":2412},{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2534,2535,2536,2537],{"name":2403,"slug":2404,"type":16},{"name":14,"slug":15,"type":16},{"name":2407,"slug":2408,"type":16},{"name":2410,"slug":2411,"type":16},{"slug":2414,"name":2414,"fn":2415,"description":2416,"org":2539,"tags":2540,"stars":23,"repoUrl":24,"updatedAt":2423},{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2541,2542],{"name":2379,"slug":2380,"type":16},{"name":2421,"slug":2422,"type":16},{"slug":2425,"name":2425,"fn":2426,"description":2427,"org":2544,"tags":2545,"stars":23,"repoUrl":24,"updatedAt":2442},{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2546,2547,2548,2549],{"name":2431,"slug":2432,"type":16},{"name":2434,"slug":2435,"type":16},{"name":2437,"slug":2438,"type":16},{"name":2440,"slug":2441,"type":16}]