Skip to content

chore(config-ui): reskin phase 0, regression harness and quality tooling - #86

Merged
ubaidkhalidarbisoft merged 7 commits into
arbisoft:feat/config-ui-reskinfrom
muzamilkm:feat/config-ui-reskin-p0-groundwork
Oct 12, 2026
Merged

ubaidkhalidarbisoft merged 7 commits into
arbisoft:feat/config-ui-reskinfrom
muzamilkm:feat/config-ui-reskin-p0-groundwork

Conversation

@muzamilkm

@muzamilkm muzamilkm commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Phase 0 of the config-ui reskin (the reskin implementation plan, section 7). It makes the code-quality rules enforceable, and turns the e2e suite into a regression harness that every later phase is measured against.

  • It adds the code-quality tooling from docs(config-ui): add lint tooling setup guide #85.
  • It changes no screen. The only app code it touches is mechanical: imports reordered and dead code removed.
  • The harness is proven green against today's UI in both auth states.

Tooling and CI

  • New package.json scripts:
    • lint:check runs tsc, then tsc -p e2e-tests/tsconfig.json, then eslint ., with no --fix.
    • lint:fix.
    • e2e:census.
    • quality:baseline.
  • Scoped strict lint for reskin-owned code (src/{ui,theme,config,routes,app}), all at error:
    • no-explicit-any;
    • exhaustive-deps;
    • no style prop;
    • no colour literals outside src/theme, including in styled-components template literals.
    • eslint.reskin-pending.mjs lists the 43 files that fail these rules today. Each phase removes the files it cleans.
  • config-ui.yml:
    • runs on PRs to main-arbisoft and feat/config-ui-reskin too;
    • uses yarn lint:check instead of tsc && eslint --fix;
    • runs yarn install --immutable;
    • bumps checkout and setup-node to v4;
    • caches Yarn 4 with corepack.
  • Empty src/ui, src/ui/hooks and src/routes/ui-kit barrels. Phase 1 fills them.

E2E regression harness

  • Page objects. Every selector, URL, click sequence and wait moved out of the 14 specs into e2e-tests/support/pages/*, and support/selectors.ts is gone.

    • A lint rule now fails any spec that calls locator, getBy*, goto, click, fill or waitForURL.
    • expects stay in the specs.
    • No test title, matcher or timeout changed.
  • Backend outcome assertions. Every UI write flow now checks the persisted result through the API, or the DB where no API exposes it. The census counts these as backend assertions.

    Area Checks added
    Local users Created with the role, plus must-change-password and password-hash checks
    User state Disable and enable
    OIDC providers Disable and enable
    OTel credentials Create, rotate, finalize, revoke and hide
    Blueprints enable after the sync policy is saved
    Rejected connection test Nothing persisted
  • Write goldens. support/write-recorder.ts is an auto fixture. It records every POST/PUT/PATCH/DELETE the UI sends, from every browser context the test opens.

    • Normalised: numeric ids become :id, e2e- names become e2e-*, the keys in GOLDEN_MASKED_KEYS are masked (the field is kept, its value isn't), and object keys are sorted.
    • Not recorded: the tests' own API calls, deliberate in-page test fetches (x-e2e-test-action), and GOLDEN_IGNORED_WRITES: connection tests (D3) and login/logout (R&D 6.1, 6.2).
    • Goldens: 66, 64 from state B and 2 from state A.
    • Stability: stable over 4 consecutive runs.
    • Content: no tokens, passwords or personal data.
  • Assertion census (e2e-tests/tools/census.mjs). It counts tests, expects, backend assertions, skips and fixmes per spec, and fails on any drop. The baseline is census.baseline.json.

  • Type-checked e2e. e2e-tests/tsconfig.json, and no any left in e2e code.

  • e2e-tests/README.md documents the page objects, goldens, census, the four test-change classes, and the new variables E2E_UPDATE_GOLDENS and PLAYWRIGHT_JSON_OUTPUT_NAME.

Quality tooling (#85)

#85's guide targets the pre-sync stack (ESLint 8, .eslintrc.js), so this ports it to ESLint 10 flat config:

  • Report-only for now. The rules live in a separate eslint.quality.config.mjs, so lint:check and the pre-commit hook are unchanged.
  • Plugin swaps:
    • eslint-plugin-import doesn't support ESLint 10, so eslint-plugin-import-x replaces it;
    • react.version is pinned, because 'detect' crashes eslint-plugin-react on ESLint 10;
    • tsconfig.eslint.json resolves the @/ alias.
  • Scripts: lint:quality, find:dead-code (knip), find:duplicates (jscpd), and quality:baseline, which fails if any per-rule count, knip finding or jscpd total grows past quality.baseline.json.
  • Docs: LINT_TOOLING_SETUP.md keeps its text and gains an "After the upstream sync (chore(config-ui): sync with upstream apache/devlake #83)" section explaining each workaround. AGENTS.md gains a short "Config UI Quality Checks" section.

First run, and what this PR fixes. Every finding was triaged before anything changed: genuine, false positive, or style-only.

Errors Warnings
First run 926 138
After rule tuning 774 249
After cleanup (the baseline) 428 250
  • Rule tuning. The rules that were mostly false positives are turned off for e2e: skipped-test guards, Playwright URL regexes, test-id randomness, cleartext test fixtures, and repeated locator strings.
    • Nested ternaries, prefer-destructuring, and the React Compiler hook rules are now warnings. This app doesn't use the React Compiler.
    • Each change carries a one-line reason in the config.
  • Import order. Fixed by the auto-fix across the tree, upstream included: 171 files, all 340 errors. A per-file script proved that only import lines moved.
  • Dead code, all of it reported by knip, upstream included:
    • 13 files: 6 orphaned styled.ts, an unused q-dev component, the bypassed slack barrel, and the old release/v0.18–v1.0 doc-link maps;
    • about 56 unused exports and types;
    • 6 unused dependencies: @ahooksjs/use-url-state, cron-validate, react-is, @testing-library/jest-dom, and the outdated @types/react-router-dom and @types/styled-components.
    • Knip now reports 0. The only ignores are the 3 empty reskin barrels.

Deferred, and how it'll be handled. The remaining findings are recorded per rule in quality.baseline.json:

  • Errors:
    • no-explicit-any 324, almost all in upstream plugin code;
    • react/no-unescaped-entities 21;
    • no-duplicate-imports 16;
    • no-unused-vars 14;
    • jsx-a11y 13;
    • array-index keys 6;
    • cognitive complexity 4, with connection-form the worst;
    • the rest are single digits.
  • Warnings:
    • exhaustive-deps 77;
    • the React Compiler hook rules 83;
    • max-lines 20;
    • other style warnings.
  • Duplication: jscpd finds 119 clones, about 80% of them upstream plugin transformation.tsx boilerplate.

They aren't fixed in bulk. Each reskin phase must leave every file it substantively edits with 0 quality errors, and quality:baseline stops the totals growing. The baseline is lowered after each phase, and the final reskin PR proposes a follow-up for whatever remains (reskin plan, section 5.6).

Regression proof

Count Before After
Tests 60 60
Expects 420 450
Backend assertions 38 66
Skips 7 7

The census before/after above is static.

E2E runs:

Run Passed Skipped Failed
State B, before 64 2 0
State B, after, on a rebuilt config-ui 64 2 0
State A 42 24 0
  • State A gives 42 rather than the 40 in chore(config-ui): sync with upstream apache/devlake #83, because the 2 theme tests came later.
  • The test diff by class:
    • locator changes are confined to support/pages/**;
    • the rest are additions (backend assertions and goldens);
    • no expectation changes.
  • The one changed check is in the state-A spec. It now compares the credential count before and after a rejected create, instead of assuming 0.
  • Capability walk: not applicable; no screen changed.

Test plan

  • lint:check: 0 errors. The 20 warnings were already there.
  • yarn test: 16 files, 91 tests.
  • yarn build.
  • yarn install --immutable.
  • e2e:census: OK.
  • quality:baseline: OK.
  • knip: 0 findings.
  • E2E state B, before and after: 64 passed, 2 skipped, with goldens matching.
  • E2E state A: 42 passed, 24 skipped. The state-A spec's goldens were generated in state A and match on a rerun.
  • Import-order commit: identical sorted lines per file, over all 171 files.
  • The write goldens were grepped for tokens, passwords and emails: none found.

Notes for reviewers

  • Six commits:
    1. e524535f0: lint gates, CI and the empty barrels.
    2. 9530b1043: page objects, backend assertions, the census and the e2e typecheck.
    3. bbaf705cd: write goldens, the census baseline and the README.
    4. 592ca7c1c: the docs(config-ui): add lint tooling setup guide #85 tooling, with rule tuning.
    5. 089aae79d: the import sort and the dead-code removal. This one is mechanical (208 files) and can be skimmed. There's no behaviour change, and the build, unit tests and full e2e passed afterwards.
    6. 9bc598045: the quality baseline and the docs.
  • Upstream coupling. The import reorder and the dead-code removal touch upstream-owned files, so future upstream cherry-picks may hit import-order conflicts. They are trivial to resolve.
  • Goldens are created only in this phase. Changing one later needs an R&D-cited expectation change in that phase's PR (reskin plan, 5.2.4).
  • The GitHub data-scope goldens were recorded with E2E_GITHUB_REPO=octocat/Hello-World.
  • CI: config-ui.yml now runs on this PR, against feat/config-ui-reskin.

@ubaidkhalidarbisoft ubaidkhalidarbisoft left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Reviewed by reading the code (I didn't run anything). Nothing blocks the merge. The main gap is that the census and the quality baseline aren't run by CI, so they only work when someone runs them by hand. Details are inline.

run: cd config-ui; yarn install --immutable
- name: Run Lint
run: cd config-ui; yarn tsc && yarn lint
run: cd config-ui; yarn lint:check

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CI doesn't run yarn e2e:census or yarn quality:baseline, and neither does the pre-commit hook (it only runs lint-staged). The PR says the census fails on any drop and the baseline stops totals growing, but nothing enforces either one on a PR. Both can run here as they are: the census is a static read of the spec files and needs no backend, and the baseline needs only the installed packages. Suggest adding:

    - name: Quality gates
      run: cd config-ui; yarn e2e:census && yarn quality:baseline

(e2e:census prints "Playwright report: none" when there's no JSON report, but its comparison only reads the spec files.)

"connectionId": "<masked>",
"createdAt": "<masked>",
"createdDate": "<masked>",
"description": "My first repository on GitHub!",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This golden stores the real GitHub repo details, including description, fullName and the clone URLs, so it depends on E2E_GITHUB_REPO being octocat/Hello-World and on GitHub never changing that repo. A different repo, or a changed description, breaks the golden even though the UI didn't change. Could the repo-derived fields be masked or normalised, so the golden only pins the shape of what the UI sends?

"labels": [],
"mode": "NORMAL",
"name": "e2e-*-Blueprint",
"plan": [

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The UI sends back the whole blueprint here, so this golden (and the sync-policy one) stores the server-generated plan. It also stores cronConfig: "15 3 * * *", which an earlier test in the same serial describe sets. A backend change to plan generation, or reordering those tests, would fail a check that is meant to catch UI changes. Could these goldens ignore plan (or keep only the fields the UI builds itself, like connections and scopes)?

export const GOLDEN_MASK = '<masked>';

// Body and query keys (case-insensitive) whose values change per run or are secrets.
export const GOLDEN_MASKED_KEYS: readonly string[] = [

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Masking only covers the field names on this list. secretKey, refreshToken, clientSecret and newPassword, and the custom-header values, are written to the goldens as-is. Today they're empty or obvious fake values, so nothing has leaked, but a future test that fills in a real secret field would commit it. Matching by pattern as well (for example /secret|token|password|apikey/i) would be safer.

return isBackendValue(node, ctx);
}

function taintedVariables(sf, backendNames) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Variables are tracked by name across the whole file, not per test. If one test does const user = await api…, then user counts as backend data in every other test too. So a later phase could switch a backend check to a UI read in one test, and the census wouldn't notice if another test reuses the name. Could this track scope (for example with ts.Symbols from a type checker), or at least note the limit in the README?

);
}
const expected = JSON.parse(fs.readFileSync(file, 'utf-8')) as Golden;
expect(actual, `UI writes differ from golden ${path.relative(GOLDENS_DIR, file)}`).toEqual(expected);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

toEqual compares the recorded writes as an ordered list, collected across every browser context. It was stable over 4 runs, but if the UI ever sends writes in parallel, the order can change between runs and the golden becomes flaky. It might be worth a comment here, or sorting writes that are known to be independent.


// Reskin-owned directories (plan 3.10); every phase removes the files it cleans from RESKIN_LINT_PENDING.
const RESKIN_OWNED = ['src/ui/**', 'src/theme/**', 'src/config/**', 'src/routes/**', 'src/app/**'];
const COLOR_LITERAL = String.raw`#[0-9a-f]{3,8}\b|rgba?\(|hsla?\(`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Minor: #[0-9a-f]{3,8}\b also matches strings that aren't colours, such as '#add' and '#feed' (anchors). It also misses named colours ('white', 'red', 'transparent'), so the "no colour literals" rule has a gap.

stdio: ['ignore', 'pipe', 'ignore'],
});
} catch (error) {
return error.stdout ?? '';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

stderr is discarded, and a failed tool returns ''. So if ESLint itself crashes (for example on a config error), all you see is Unexpected end of JSON input from JSON.parse. Suggest piping stderr through, or throwing with error.stderr when stdout is empty.

const baseline = JSON.parse(fs.readFileSync(BASELINE, 'utf8'));
const problems = [];

for (const [rule, now] of Object.entries(current.eslint)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This only fails when counts go up. When they go down, nothing updates the baseline, so a fixed knip finding or rule error stays allowed and could come back. The docs say to run --write after each phase. Printing "counts dropped, run --write" here would make that hard to forget.

@ubaidkhalidarbisoft
ubaidkhalidarbisoft merged commit a2f03a3 into arbisoft:feat/config-ui-reskin Oct 12, 2026
6 checks passed
@ubaidkhalidarbisoft

Copy link
Copy Markdown

Automated React review using the react-review Claude Code skill (#101). Scope: this PR's own commits only (stacked changes excluded). Findings are model-generated; verify before acting, especially items tagged (verify).

React Review: PR #86 — chore(config-ui): reskin phase 0, regression harness and quality tooling

Range: git diff 2891a4181...pr-86 -- config-ui/src (7 own commits). The production src change comes from 089aae79d refactor(config-ui): sort imports and remove dead code and dependencies and e524535f0 (empty UI barrels).

Coverage

177 production files in scope (164 modified, 3 renamed, 10 deleted). Tests, e2e, goldens, lockfiles and tooling configs were not reviewed as code. I read the ESLint/knip configs only to learn which rules are enforced.

  • Read in full at PR head (28): api/index.ts, api/plugin/index.ts, api/plugin/webhook/index.ts, app/store.ts, features/theme/slice.ts, plugins/register/webhook/index.ts, plugins/register/webhook/components/selector-dialog.tsx, plugins/register/webhook/components/create-dialog.tsx, ui/index.ts, ui/hooks/index.ts, routes/ui-kit/index.ts, config/entities.ts, routes/project/utils.ts, plugins/register/webhook/styled.ts, plugins/components/connection-form/fields/styled.ts, plugins/components/data-scope-remote/styled.ts, plugins/components/data-scope-remote/types.ts, plugins/register/github/connection-fields/styled.ts, routes/blueprint/connection-detail/styled.ts, routes/blueprint/detail/components/sync-policy/styled.ts, routes/blueprint/detail/styled.ts, routes/blueprint/home/styled.ts, routes/onboard/styled.ts, routes/project/detail/styled.ts, theme/ThemeProvider.tsx, hooks/use-refresh-data.ts, components/markdown/index.tsx, routes/login/index.tsx.
  • Read in part (diff plus the relevant sections) (8): features/connections/slice.ts (export surface), plugins/register/youtrack/transformation-fields/field-names.tsx (lines 17–60), routes/otel/utils.ts (diff plus lines 80–110), routes/access/utils.ts (diff plus helper usages), plugins/components/scope-config-form/index.tsx (imports plus the branching block), routes/connection/connection.tsx (import block), plugins/register/index.ts (import block), plugins/components/connection-form/fields/rate-limit.tsx (the dangerouslySetInnerHTML site).
  • Deleted files, read from the diff (10): azure/connection-fields/styled.ts, bamboo/styled.ts, jira/connection-fields/styled.ts, opsgenie/connection-fields/styled.ts and q-dev/connection-fields/connection-test.tsx were read fully. release/v0.18.ts, v0.19.ts, v0.20.ts, v0.21.ts and v1.0.ts were skimmed: they are static doc-URL maps, and release/index.ts only re-exports ./stable, so they were unreferenced. The renamed-away slack/index.ts, scope-config-form/styled.ts and components/block/styled.ts were also read from the diff.
  • Diff-only: I reviewed the changed hunk and did not read the file body (131). In every one of these files the only change is the import block (reordering, plus adding or removing blank lines between import groups). I also ran a repo-wide grep for security sinks over all 177 files (dangerouslySetInnerHTML, rehypeRaw, innerHTML, storage, window.location/open, target="_blank", postMessage, eval). Files:
    api/project/index.ts, components/action/copy-text/index.tsx, components/action/icon-button/index.tsx, components/logo/index.tsx, components/no-data/index.tsx, components/page-header/index.tsx, components/tooltip/help-tooltip/index.tsx, components/tooltip/text-tooltip/index.tsx, features/connections/utils.ts, main.tsx, plugins/components/connection-form/fields/{app-id,endpoint,index,name,password,proxy,secret-key,token,username}.tsx, plugins/components/connection-form/index.tsx, plugins/components/connection-list/index.tsx, plugins/components/connection-status/index.tsx, plugins/components/data-scope-remote/{data-scope-remote,search-local,search-remote}.tsx, plugins/components/data-scope-select/index.tsx, plugins/components/plugin-name/index.tsx, plugins/components/scope-config-select/index.tsx, plugins/components/scope-config/index.tsx, plugins/register/azure/connection-fields/{base-url,organization}.tsx, plugins/register/bamboo/transformation.tsx, plugins/register/bitbucket-server/transformation.tsx, plugins/register/bitbucket/connection-fields/auth.tsx, plugins/register/bitbucket/transformation.tsx, plugins/register/claude-code/connection-fields/{custom-headers,organization,token}.tsx, plugins/register/codex/config.tsx, plugins/register/gh-copilot/connection-fields/{enterprise,organization}.tsx, plugins/register/github/connection-fields/{authentication,githubapp,token}.tsx, plugins/register/github/transformation.tsx, plugins/register/gitlab/transformation.tsx, plugins/register/hubspot/config.tsx, plugins/register/jira/connection-fields/auth.tsx, plugins/register/jira/transformation-fields/{cross-domain,dev-panel,remote-link}.tsx, plugins/register/jira/transformation.tsx, plugins/register/notion/config.tsx, plugins/register/opsgenie/connection-fields/endpoint.tsx, plugins/register/plane/connection-fields/workspace-slug.tsx, plugins/register/q-dev/connection-fields/{aws-credentials,identity-center-config,s3-config}.tsx, plugins/register/q-dev/data-scope.tsx, plugins/register/sonarqube/config.tsx, plugins/register/sonarqube/connection-fields/organization.tsx, plugins/register/tapd/config.tsx, plugins/register/tapd/connection-fields/company-id.tsx, plugins/register/tapd/transformation.tsx, plugins/register/teambition/config.tsx, plugins/register/teambition/connection-fields/{tenant-id,tenant-type}.tsx, plugins/register/webhook/components/{delete-dialog,edit-dialog,view-dialog}.tsx, plugins/register/webhook/connection.tsx, plugins/register/youtrack/connection-fields/endpoint.tsx, plugins/register/youtrack/transformation-fields/mapping-table.tsx, plugins/register/youtrack/transformation.tsx, plugins/register/zentao/config.tsx, plugins/register/zentao/connection-fields/db-url.tsx, plugins/utils.ts, routes/access/{access,authentication-columns,authentication-editor,authentication,columns,modals}.tsx, routes/access/{loader,use-account-menu}.ts, routes/api-keys/api-keys.tsx, routes/blueprint/connection-detail/{index,table}.tsx, routes/blueprint/detail/{blueprint-detail-page,blueprint-detail,configuration-panel,status-panel}.tsx, routes/blueprint/detail/components/{add-connection-dialog,sync-policy,update-name-dialog,update-policy-dialog}/index.tsx, routes/blueprint/home/index.tsx, routes/blueprint/home/utils.ts, routes/change-password.tsx, routes/connection/connections.tsx, routes/connection/styled.ts, routes/db-migrate/index.tsx, routes/error/index.tsx, routes/layout/layout.tsx, routes/layout/loader.ts, routes/not-found/index.tsx, routes/onboard/components/card.tsx, routes/onboard/{index,step-0,step-1,step-2,step-3,step-4}.tsx, routes/otel/{attention,columns,ingestion-health,modals,otel,source-policy}.tsx, routes/pipeline/components/{info,status,table,task,tasks}.tsx, routes/pipeline/pipeline.tsx, routes/project/detail/{claude-code-otel-columns,claude-code-otel-panel,index,settings-panel,webhooks-panel}.tsx, routes/project/home/index.tsx.

Verification done: for every export the PR removed or un-exported (API, plugin.list, AppThunk, the two slice default exports, setMode, THEME_STORAGE_KEY, EntitiesLabel, SelectorDialog, SlotKey, LOCAL_CREDENTIAL_ERROR, getOIDCProviderErrorCode, formatConnectionCount, withVerb, validName, QDevConnectionTest, the removed styled components and types), I grepped src and e2e-tests and found no remaining external references. No test was deleted or weakened; the test-file changes are import reordering only. I could not run tsc: the main checkout's node_modules TypeScript version rejects ignoreDeprecations: "6.0". The PR states that CI's lint:check (including tsc) passes.


Summary: In src, the PR does what it claims: imports are reordered and dead code removed. I found no behaviour change, no runtime risk and no security regression. The findings are cleanup that is half-done or inconsistent (an orphaned reducer, a mixed webhook dialog export style, the slack plugin barrel deleted instead of used) plus a few nits. Every security item below is pre-existing and low risk.

🔴 Critical 🟠 Major 🟡 Minor ⚪ Nit
0 0 3 4

🟡 Minor

1. setMode reducer left orphaned after its action was un-exported — src/features/theme/slice.ts:66, :77

Area: Maintainability → Hygiene (dead code)
Why: The PR changed export const { setMode, cycleMode } to export const { cycleMode } and kept the setMode case reducer. Nothing can dispatch it now. knip stops reporting it because it no longer looks at the reducer, so the dead branch stays hidden. The reskin's theme picker will need an explicit "set mode" action. Either delete the reducer or keep its export on purpose. Hiding the export but keeping the code is the worst of both.
Fix: choose one.

// a) keep it as public API for the phase-1 theme picker
export const { setMode, cycleMode } = themeSlice.actions;
// and add it to the knip ignore list, with a comment, like the empty ui barrels

// b) or delete it
reducers: {
  cycleMode(state) { … },
},

2. Webhook dialogs now use mixed export styles, and one barrel line re-exports nothing — src/plugins/register/webhook/components/selector-dialog.tsx:37,80, src/plugins/register/webhook/components/index.ts:22, src/plugins/register/webhook/index.ts:22-23

Area: Architecture → Barrels
Why: The dead-code pass cleaned up the two dialogs in opposite directions:

  • CreateDialog lost its default export and kept the named one.
  • SelectorDialog lost its named export and kept default.

As a result, components/index.ts:22's export * from './selector-dialog' now re-exports nothing (export * skips defaults). The public barrel also mixes { CreateDialog as … } with { default as … }. Every other dialog in this folder uses a named export.
Fix: use a named export everywhere and drop the default.

// selector-dialog.tsx
export const SelectorDialog = ({ open, saving, onCancel, onSubmit }: Props) => { … };
// (remove `export default SelectorDialog;`)

// webhook/index.ts
export { CreateDialog as WebhookCreateDialog, SelectorDialog as WebhookSelectorDialog } from './components';

3. Slack plugin barrel deleted instead of used — src/plugins/register/index.ts:49, src/plugins/register/slack/ (was slack/index.ts)

Area: Architecture → Barrels / folder-per-concern consistency
Why: knip reported slack/index.ts as unused because register/index.ts deep-imports ./slack/config. The PR removed the barrel and kept the deep import. Slack is now the only plugin folder under register/ (apart from the asset-only claude_otel) without an index.ts. Every other entry imports through ./<plugin>. The next person who adds a Slack transformation or connection field will find no public entry point.
Fix: fix the import instead of deleting the barrel.

// plugins/register/slack/index.ts
export * from './config';

// plugins/register/index.ts
import { SlackConfig } from './slack';

⚪ Nits

  1. Duplicate imports left adjacent by the sort (src/routes/connection/connection.tsx:31-32, :33-41). The file imports '@/hooks' twice and '@/plugins' twice. The sort put each pair next to each other but did not merge them. no-duplicate-imports in the quality config reports this (baseline: 16). Merging now is free and lowers the baseline.
    import { useAppDispatch, useAppSelector, useRefreshData } from '@/hooks';
    import { ConnectionStatus, DataScopeRemote, getPluginConfig, getPluginScopeId, getPluginScopeName, ScopeConfig, ScopeConfigSelect } from '@/plugins';
  2. Empty styleless wrappers kept inconsistently. The PR deleted the empty Wrapper = styled.div``` in data-scope-remote/styled.ts and scope-config-form/styled.ts, but kept the identical no-op wrappers in routes/blueprint/home/styled.ts:21 (DialogWrapper), routes/project/detail/styled.ts:21 (Wrapper) and plugins/components/scope-config/index.tsx:34. knip keeps them because they are referenced. Replace them with a fragment or plain div in the same pass.
  3. Placeholder barrels holding only export {} (src/ui/index.ts, src/ui/hooks/index.ts, src/routes/ui-kit/index.ts) plus their knip ignores. These are speculative shared modules. They are fine as phase-1 scaffolding, but git recorded them as renames of components/block/styled.ts, scope-config-form/styled.ts and slack/index.ts (similarity 75–96%, because only the licence header matches). That makes git log --follow on the new files misleading. Next time, add new files in a separate commit from the deletions.
  4. Component file convention (reported once). This codebase uses <name>.tsx + index.ts per component folder, while the team standard says <name>/index.tsx. The PR does not change this, so I report it only once.

Pre-existing (outside this change)

Security, reported in full as the standard requires. Both are low risk today:

  • Markdown renders raw HTML (src/components/markdown/index.tsx:33, rehypePlugins={[rehypeRaw]}). Today the only callers are the onboarding steps (for example routes/onboard/step-1.tsx:39), and they feed it same-origin static /onboard/*.md, so it is not exploitable now. Because it is a shared component, the first caller that passes API or user text gets XSS. Add sanitisation:
    import rehypeSanitize from 'rehype-sanitize';
    <ReactMarkdown rehypePlugins={[rehypeRaw, rehypeSanitize]} …>
  • dangerouslySetInnerHTML for rate-limit help (src/plugins/components/connection-form/fields/rate-limit.tsx:89). externalInfo comes only from static plugin configs, so the risk is low. Type the prop as ReactNode and render it as children, so the HTML sink goes away.

Other, one line each:

  • src/api/project/index.ts:19 imports encodeName from the @/routes barrel. That is an api → routes layer inversion and creates a cycle (routes → @/api → project → @/routes). Move encodeName to @/utils. Note that the import reordering in this PR changes evaluation order inside such cycles; the e2e harness passing is the only proof it is safe.
  • src/plugins/components/scope-config-form/index.tsx:205+ has 17 plugin === '…' && <XTransformation/> branches with static imports of every plugin. Add a transformation component (lazy) to the IPluginConfig registry instead.
  • src/hooks/use-refresh-data.ts:44-70 runs side effects during render (setTimeout, abort). It has no unmount cleanup, so setVersion can run after unmount, and its error handling is console.error.
  • react-hooks/exhaustive-deps is off globally outside the reskin-owned folders (77 warnings in the quality baseline).
  • Hard-coded colours in src/plugins/components/data-scope-remote/styled.ts:27-31 (#d9d9d9, #fafafa, #8c8c8c).
  • Relative cross-module imports instead of @/: routes/otel/utils.ts:21-22, routes/access/utils.ts:28, routes/blueprint/home/utils.ts:19-20.
  • Deep import @/components/block instead of the barrel in plugins/register/{codex,cursor,hubspot,notion,salesforce}/config.tsx.
  • plugins/register/youtrack/transformation-fields/field-names.tsx:24 exports the type FieldDef from a component file.

Done well

  • The cleanup is behaviour-neutral and I could verify it. Every removed or un-exported symbol has zero remaining references, and no test was removed or weakened.
  • It removes real dead weight: the unused QDevConnectionTest (190 lines of hand-rolled try/catch/loading code), five stale versioned doc-URL maps, and duplicated Label/LabelInfo styled blocks.
  • The strict lint is scoped to reskin-owned code, with a shrinking RESKIN_LINT_PENDING list. That is a sound ratchet: new code meets the bar without a big-bang refactor.

🤖 Generated with Claude Code

@ubaidkhalidarbisoft

Copy link
Copy Markdown

Review: approve with nits

Reviewed 2891a4181..6d666aaf4. The src/ changes are only import moves and dead-code removal. There are no backend, Grafana or migration changes.

1. Medium: CI doesn't run the census or the quality baseline. .github/workflows/config-ui.yml:44-46

  • CI only runs yarn lint:check and yarn test. Neither yarn e2e:census nor yarn quality:baseline runs anywhere, in CI or the pre-commit hook.
  • The PR and AGENTS.md say the census fails on any drop and the baseline stops totals growing, so a later phase could remove expects or add no-explicit-any errors and CI would stay green.
  • Fix: add a yarn e2e:census step (it's static and needs no browser), plus yarn quality:baseline, at least on PRs into feat/config-ui-reskin.

2. Low: some secret keys aren't masked in goldens. config-ui/e2e-tests/support/constants.ts:26-44

  • GOLDEN_MASKED_KEYS misses secretKey, refreshToken, secretAccessKey, accessKeyId, appSecret, newPassword and custom-header value.
  • They already appear unmasked (as empty strings) in goldens/connections/connection-lifecycle/create-test-save-and-edit-the-name-of-a-connection--76fb351f.json.
  • A future spec that uses real values with E2E_UPDATE_GOLDENS=1 would commit the secret. Fix: extend the list, or mask any key matching /secret|token|password|key$/i.

3. Low: the census over-counts backend assertions. config-ui/e2e-tests/tools/census.mjs:98-108, 120, 126, 167-195

  • Every named import from support/api counts as a backend source, including uniqueName and DUMMY_TOKEN.
  • Taint is tracked per file, not per test. In connections/catalog.spec.ts:71-73, names comes from UI text, but a names declared in another test makes it count. The reported 66 should be 65.
  • Fix: allowlist only the functions that do I/O, and scope taint to the enclosing test callback.

4. Low: removing react-is leaves a peer dependency unmet. config-ui/package.json

  • styled-components@5.3.11, pulled in by miller-columns-select, declares react-is as a peer. It resolves today only because some transitive version happens to be hoisted.
  • Fix: restore react-is, or add a knip exception with a comment.

Nits

  • format-jscpd.cjs and tools/quality-baseline.mjs throw away stderr and the exit code. A jscpd crash prints "No duplicates found.", and an ESLint config error shows up as a bare JSON.parse error.
  • The data-scope goldens pin the live octocat/Hello-World description, so they fail if GitHub changes it.

🤖 Generated with Claude Code

@muzamilkm
muzamilkm deleted the feat/config-ui-reskin-p0-groundwork branch October 16, 2026 09:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants