Skip to content

Add ACP IT Solutions AG, rename AppBuilder to Tektonics Systems - #615

Merged
vvlladd28 merged 5 commits into
thingsboard:mainfrom
rusikv:add-acp-rename-tektonics
Aug 20, 2026
Merged

Add ACP IT Solutions AG, rename AppBuilder to Tektonics Systems#615
vvlladd28 merged 5 commits into
thingsboard:mainfrom
rusikv:add-acp-rename-tektonics

Conversation

@rusikv

@rusikv rusikv commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Two partner-requested updates to the distributor list, plus the capability the first of them turned out to need.

Partner changes

  • Added ACP IT Solutions AGdevelopment.hzb@acp.de, acp-gruppe.com/de-at, EMEA coverage. Inserted after ACORDE to hold the A-cluster's alphabetical order. Its website keeps the supplied /de-at path rather than being normalised to a bare domain, so the link lands on the Austrian page directly.
  • Renamed AppBuilder (Pty.) Ltd.Tektonics Systems, with the new address tshepo@tektonics.africa and domain tektonics.africa. Coverage unchanged (Africa / South Africa) as requested. The entry moves out of the A-cluster to sit between Techno Pynė and Tesla Water — the page renders array order without sorting, so alphabetical position is maintained by hand. No appbuilder references remain in src/ or public/.

Region-wide coverage

ACP serves EMEA, which the data model could not express: coverage was an enumerated country list, and leaving it empty dropped the card out of every country filter, since filtering tests membership of data-countries and an empty attribute splits to [""].

Coverage is now one field. A distributor either lists its countries or sets countries: 'region-wide':

countries: [string, ...string[]] | 'region-wide';
  • Region-wide coverage is expanded at build time into the concrete countries its regions contain, so the client keeps a single filtering rule — a card matches if its country list contains the selection — rather than growing a second, region-aware path that could drift from the server's view.
  • Expansion is deliberately kept out of the dropdown's options: it widens what an existing option matches and adds none of its own, so the finder's dropdown behaves exactly as it does today. Deriving the dropdown from the new table is left as a follow-up, since it changes what users see.

Why an explicit table rather than deriving it

regionCountries only looks like a country→region map. It is a cross-product of each entry's countries with each of its regions, so Teknei (Europe + North America + South America, lists the USA) files the United States under Europe.

Derivation EMEA match set Verdict
Cross-product (regionCountries) 113 Wrong — USA, Canada, Mexico, Kazakhstan match an EMEA partner
Single-region entries only 30 Too narrow — loses Saudi Arabia, Turkey
Intersection of each country's entries' regions 65 Loses Belarus, Moldova, Armenia, Azerbaijan
Explicit table 67 Correct in both directions

src/data/partners/regions.ts classifies the 168 countries the dataset uses into the seven finder regions. Transcontinental countries carry every region they span (Turkey → Europe + Middle East; Armenia, Azerbaijan, Georgia → Europe + Asia), and Middle East stays a region of its own rather than a subset of Asia, matching how the existing entries are written.

Where the invariants live

Every failure mode here is invisible on the page — a card missing from a filter looks identical to a partner who does not cover that country — so none of it is left to convention. Three of the four checks are now compile errors:

Invariant Enforced by
A region name must be real regions: Region[], from REGIONS as const
A country list must be non-empty [string, ...string[]] rejects []
The table must cover exactly the seven regions Record<Region, string[]>
The table and the data must name the same countries findCoverageErrors, at import time

src/data/partners/index.ts owns the last one and the derived selectors (getCoverage, getNamedCountries, COVERED_COUNTRIES), matching how src/data/use-cases/ and src/data/case-studies/ validate at module load. Consumers import @data/partners, so the checks fail for every consumer rather than only for the page that happens to remember them.

scripts/lint-distributors.ts + a distributors job in ci.yml run this in the GitHub Actions lane, which previously ran no full build and therefore never evaluated the page's frontmatter at all.

Country-name normalisation

SwizerlandSwitzerland, and the redundant Lietuvos Respublika and Kingdom of the Netherlands are gone — each sat alongside the canonical spelling in the same entry and appeared as its own dropdown option. The table would otherwise have had to enshrine them. The dropdown goes from 171 options to 168.

Verification

  • pnpm build:fast — full build, which exercises the page and its data module across every route.
  • pnpm check clean (0 errors / 0 warnings / 0 hints); ESLint clean; pnpm lint:distributors passes (100 distributors, 168 countries offered, 1 covering whole regions).
  • Negative tests, since a check that cannot fail is worse than no check: a probe with regions: ['Middle east'] and countries: [] produced exactly two pnpm check errors, including "Did you mean 'Middle East'?"; injecting an unclassified country into a distributor made lint:distributors exit 1 with an actionable message. Both fixtures in the lint script were mutation-tested — disabling either half of findCoverageErrors fails that half's assertion and only that one.
  • Live filter matrix: Europe+Austria → ACP shown; all-regions+USA → hidden; Middle East → shown; Asia → hidden; Europe+Belarus → shown; Africa+South Africa → ACP and Tektonics shown.

Follow-ups, not in this PR

  • Derive the region→country dropdown from REGION_MEMBERSHIP, so selecting Europe stops offering the USA (102 offered today, 47 of them not European). This also resolves Syria being offered only under Asia.
  • Audit the remaining 168 country names for further alternate spellings and typos.

Two partner-requested changes to the distributor list. ACP is a new entry.
The South African partner listed as AppBuilder (Pty.) Ltd. now trades as
Tektonics Systems, with a new contact address and domain but unchanged
coverage.

ACP's stated coverage is EMEA, which has no single value here — the filter
model is seven fixed regions plus an explicit country list — so it maps to
Europe, Middle East and Africa with no countries. The card therefore shows
up under those three region filters but drops out as soon as a visitor
picks a specific country, since the country filter requires a match. It is
the only entry of the hundred without countries; a country list from the
partner would close that gap.

Its website keeps the /de-at path it was supplied with rather than being
normalised to a bare domain like its neighbours, so the link lands on the
Austrian page directly.

The renamed entry moves out of the A-cluster to sit between Techno Pynė and
Tesla Water: the page renders array order without sorting, so alphabetical
position is maintained by hand.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Updates the partners/distributors dataset used by the /partners/distributors page by adding a new distributor entry and renaming an existing distributor (with updated contact details) to reflect partner-requested changes.

Changes:

  • Added a new distributor entry for ACP IT Solutions AG (regions: Europe/Middle East/Africa).
  • Renamed AppBuilder (Pty.) Ltd. to Tektonics Systems and updated email/website while keeping coverage the same.
  • Adjusted the in-array placement to preserve the page’s manual alphabetical ordering.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +36 to +40
name: "ACP IT Solutions AG",
regions: ["Europe","Middle East","Africa"],
countries: [],
email: "development.hzb@acp.de",
website: "acp-gruppe.com/de-at",

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Went with (b), with the geography made explicit rather than inferred.

regionWide: true now marks an entry as covering its whole regions, and the build expands that into the concrete countries those regions contain. The client keeps one filtering rule, and data-countries is never empty for such an entry — so no wildcard branch, and no second region-aware code path to drift from the server's view.

Worth recording why the mapping is a new table rather than derived from what was already here: regionCountries looks like a country→region map, but it is a cross-product of each entry's countries with each of its regions. Teknei (Europe + North America + South America) lists the United States, so the USA lands in Europe's country set and an EMEA partner would have matched it. Deriving per country from the intersection of its entries' regions excludes the USA correctly, but then loses Belarus, Moldova, Armenia and Azerbaijan, which no single-region entry claims. Hence src/data/partners/regions.ts: the 168 countries the dataset uses, classified once.

Three checks now fail the build rather than degrade quietly, which also covers the case you were guarding against — someone adding an entry and simply forgetting the countries:

  1. an entry with no countries and no regionWide;
  2. a country absent from the table;
  3. table keys that are not exactly REGIONS.

Expansion is deliberately kept out of allCountries, so the dropdown's options are unchanged by this PR. ACP now matches 67 EMEA countries and nothing outside them — verified in the built HTML, which also confirms the guards execute.

ACP serves EMEA, which the data model could not express: coverage was an
enumerated country list, and leaving it empty dropped the card out of every
country filter, since filtering tests membership of data-countries and an
empty attribute splits to [""].

Adds regionWide to Distributor. Coverage for such an entry is expanded at
build time into the concrete countries its regions contain, so the client
keeps a single filtering rule — a card matches if its country list contains
the selection — rather than growing a second, region-aware path that could
drift from the server's view. The expansion is deliberately kept out of
allCountries: it widens what an existing dropdown option matches and adds
no options, so the finder's dropdown behaves exactly as before.

Expanding needs to know which countries a region contains, and nothing in
the repo knew. regionCountries only looks like that map; it is a
cross-product of each entry's countries with each of its regions, so Teknei
(Europe, North America, South America) files the United States under Europe
and an EMEA partner would match it. Deriving per country from the
intersection of its entries' regions fixes that but loses Belarus, Moldova,
Armenia and Azerbaijan, which no single-region entry claims. Hence an
explicit table: the 168 countries the data uses, classified once.

Transcontinental countries carry every region they span, so Turkey counts
as Europe and Middle East. Middle East stays a region of its own rather
than a subset of Asia, matching how the existing entries are written.

Three checks fail the build rather than degrade quietly, because every
failure here is invisible on the page — a card silently missing from a
filter looks identical to a partner who does not cover that country. An
entry with no countries and no regionWide throws, a country absent from the
table throws, and the table's keys must be exactly the finder's regions.

Normalises three country names the table would otherwise have had to
enshrine: Swizerland to Switzerland, and the redundant Lietuvos Respublika
and Kingdom of the Netherlands, each of which sat alongside the canonical
spelling in the same entry and appeared as its own dropdown option.

@vvlladd28 vvlladd28 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review summary

Reviewed 4 changed files in Add ACP IT Solutions AG, rename AppBuilder to Tektonics Systems. Left 9 comment(s) inline.

The data changes and the reasoning behind the explicit table both hold up under checking: REGION_MEMBERSHIP's union is exactly the 168 countries the dataset uses with no extras in either direction, ACP expands to exactly 67 countries, alphabetical placement of both entries is right, and no distributor currently carries a region name outside REGIONS. Deriving coverage at build time is also the right call over pasting the 67 names into the data file. The substantive comments are about where that derivation and its guards live, one guard that fails open, and the fact that the new table now competes with the pre-existing regionCountries cross-product.

Additional findings

These observations are about existing code outside the PR's diff — spotted while reading surrounding context.

  • src/pages/partners/distributors.astro:145 — Syria is the one place where the new table and the existing dropdown disagree visibly. REGION_MEMBERSHIP files it under Middle East, but HADEX (its only lister) is Asia, so regionCountries offers Syria only in the Asia group. Selecting Middle East therefore never offers Syria, and selecting Asia + Syria excludes ACP. Every other table pair is reachable. This dissolves if the dropdown moves onto REGION_MEMBERSHIP as suggested on src/data/partners/regions.ts:16.

This review was auto-generated. Findings may contain errors — please verify before applying changes.

Comment thread src/pages/partners/distributors.astro Outdated
import { DISTRIBUTORS, REGIONS } from '~/data/partners/distributors';
import { REGION_MEMBERSHIP } from '@data/partners/regions';

const uncovered = DISTRIBUTORS.filter((d) => !d.regionWide && d.countries.length === 0);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Deriving the expansion at build time is the right call over pasting 67 country names into the ACP entry — the data would then encode a snapshot instead of the intent ("all of EMEA") and would rot silently as the dataset grows. What I'd question is this page's frontmatter as the home for it.

The repo already has a convention for exactly this: src/data/use-cases/index.ts and src/data/case-studies/index.ts both validate invariants and export derived shapes at module load, and src/data/partners/hardware-partners.ts exposes derived selectors (getSortedPartners(), getPartnerBySlug()). After this PR, distributors.astro is the only file under src/pages/ that throws.

Would you consider moving the three guards plus the expansion into src/data/partners/ — say an index.ts re-exporting DISTRIBUTORS, REGIONS and a derived coverage map or getCoverage(d)? A module that throws at import time still fails the build, but it fails for every consumer, so a future map or partner page can't reintroduce the unexpanded-coverage bug. It also makes the logic importable without rendering, and lets the messages pick up the [distributors] prefix the other two collections use.

Comment thread src/pages/partners/distributors.astro Outdated
// Fail closed: an unclassified country would quietly narrow a region-wide
// partner's coverage instead of erroring.
const classified = new Set(Object.values(REGION_MEMBERSHIP).flat());
const unclassified = Array.from(new Set(DISTRIBUTORS.flatMap((d) => d.countries))).filter(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Two things about this check.

It's one-directional: it catches a dataset country missing from the table, but not a table entry that's absent from the dataset. The two sides do match exactly right now (168 both ways, I checked), but a typo introduced into REGION_MEMBERSHIP later — "Untied Arab Emirates" — would expand into a country nobody can select and quietly narrow a region-wide partner's coverage. That's the same invisible failure the guards exist to catch, and the reverse check is one more line over the same two sets.

Separately, Array.from(new Set(DISTRIBUTORS.flatMap((d) => d.countries))) is computed here and again as allCountries 26 lines down. Hoisting the one set and having both read from it keeps them from drifting apart later, and the guard's intent reads more clearly as "every country the finder can offer must be classified" when it's literally checking allCountries.

Comment thread src/pages/partners/distributors.astro Outdated
(c) => !classified.has(c),
);
if (unclassified.length > 0) {
throw new Error(`Countries missing from REGION_MEMBERSHIP: ${unclassified.join(', ')}`);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Worth thinking about when these guards actually fire. There's no test suite in this repo, so build-time throws are a reasonable substitute — but GitHub Actions runs astro check, eslint and lint:slugcheck, none of which evaluate this page's frontmatter. The throw only happens in a full astro build, which currently lives in the separate linkcheck pipeline.

Probably good enough today. But the repo's own pattern for data invariants that must fail fast is a standalone script in scripts/ (lint-slugcheck.mjs, check-redirect-chains.ts) wired into the fast lane. If the guards move into src/data/partners/, a small scripts/lint-distributors.ts importing that module would give the same checks in CI — and would be the natural place to assert the interesting cases: a region-wide entry expands to its regions' union, a normal entry is untouched, an unclassified country trips the guard.

Comment thread src/pages/partners/distributors.astro Outdated
// country their regions contain. Deliberately not fed into allCountries above:
// expansion widens what an existing dropdown option matches, it adds no options.
const filterCountries = d.regionWide
? Array.from(new Set([...d.countries, ...d.regions.flatMap((r) => REGION_MEMBERSHIP[r] ?? [])])).sort()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

REGION_MEMBERSHIP[r] ?? [] is the one place here that fails open, and it's reachable. Nothing validates that a distributor's regions are actual REGIONS members — the field is plain string[] — so a typo on a region-wide entry ("Middle east") contributes zero countries at this line. If that's its only region, filterCountries comes out empty, data-countries renders as "", and ''.split('|') yields [''] on the client, so the card drops out of every country filter. That's precisely the failure mode the first guard was written to prevent, except the guard passes because regionWide is set — and the region filter would silently miss the card too.

Cheapest fix is a fourth guard over DISTRIBUTORS.flatMap((d) => d.regions). Better: both this and the REGION_MEMBERSHIP-keys guard become compile errors caught by astro check if REGIONS gets as const plus export type Region = (typeof REGIONS)[number], then Record<Region, string[]> on the table and regions: Region[] on Distributor. I checked every entry — none has a bad region today, so that retype should be clean.

Comment thread src/pages/partners/distributors.astro Outdated
const mailHref = isEmail ? `mailto:${d.email}?subject=Introduction request` : '';
return {
...d,
filterCountries,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The mapped object now carries both countries (raw, empty for a region-wide partner) and filterCountries (expanded), which makes it possible for a future template edit to reach for the wrong one and quietly render a card that matches nothing. Since nothing downstream uses the raw list, overriding it in the spread (countries: filterCountries) — or dropping ...d for an explicit view model — removes the choice entirely. If you'd rather keep both, coverageCountries reads better than filterCountries, which names the consumer rather than the thing.

Comment thread src/pages/partners/distributors.astro Outdated
{
distributors.map((d) => (
<div class="distributor-box" data-regions={d.regions.join('|')} data-countries={d.countries.join('|')}>
<div class="distributor-box" data-regions={d.regions.join('|')} data-countries={d.filterCountries.join('|')}>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Minor, but the expansion moves ~1KB of country names into the HTML per region-wide card (67 names for ACP), on top of the region-countries JSON blob the page already ships — and it scales with region-wide partners × region size, so an APAC-wide plus an Americas-wide entry would add a few more KB to a page that recently got LCP attention.

If those entries multiply, the shape to reach for is emitting data-region-wide on the card and letting filterAndUpdate treat it as matching when the selected country appears in one of the region lists it already has in REGION_COUNTRIES — it's already reading data-regions, so it'd be a couple of lines and no expanded attribute at all. Not worth it for a single partner.

Comment thread src/data/partners/types.ts Outdated
regions: string[];
countries: string[];
/** Serves every country in its regions; leave countries empty. */
regionWide?: boolean;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This flag only means anything in combination with countries, and the invariant ("leave countries empty") lives in a doc comment plus a runtime guard rather than in the type. The guard covers the empty-countries-without-flag half; nothing describes regionWide: true with a non-empty countries, which the expansion silently unions into a third, undocumented mode.

Have you considered expressing coverage as one field so the states are exclusive by construction? countries: string[] | 'region-wide' is the smallest change and reads well at the call site; a discriminated coverage: { kind: 'countries'; countries: string[] } | { kind: 'regions' } is more explicit if you expect a third mode later. Either shape makes the first build-time guard unnecessary and stops a future entry from being written with countries: [] and no flag in the first place.

Comment thread src/data/partners/regions.ts Outdated
* counts as Europe and Middle East, and Georgia as Europe and Asia.
* - Caribbean and Central American territories count as North America.
*/
export const REGION_MEMBERSHIP: Record<string, string[]> = {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This table and the pre-existing regionCountries cross-product in the page are now two answers to the same question, and this is the correct one. Its union is exactly the 168 countries the dataset uses — a clean classification of the same universe regionCountries approximates by unioning the country lists of every distributor that serves a region. That approximation is badly off: picking Europe in the finder offers 102 countries, 47 of which aren't in Europe; North America offers 108, of which 67 belong elsewhere (they leak in via multi-region entries like IoT Squad listing Europe + North America).

Since you're paying to maintain the authoritative mapping anyway, could the dropdown be built from it instead — REGION_MEMBERSHIP[region] intersected with the countries actually covered — deleting the regionCountries loop? That makes the table earn its keep twice. It's a visible change to dropdown contents, so it may genuinely belong in the follow-up you flagged rather than here; mostly I want to name the duplication before it ossifies, since right now the accurate map decides which cards match while the inaccurate one decides what the user can pick.

* finder can actually offer need an entry here, and the distributors page fails
* the build when a distributor introduces one that is missing.
*
* Conventions:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Documenting the classification conventions here is the genuinely valuable part — it's what a reviewer can't otherwise verify. The cost worth naming in the same comment is that this table's union now has to stay equal to the dataset's country union (it is, exactly 168 both ways), so every ordinary "add a distributor" edit that introduces a new country becomes a two-file change. The guard makes that loud rather than silent, which is the right trade, but a line like "adding a country to any distributor requires adding it here" would put the instruction in front of the next person before the build error does.

The alternative shape would be Record<string, Region[]> keyed by country: one row each, no duplicated entries for the four transcontinental cases, and region → countries derivable at load. It reads less like the geography though, so I'd only switch if the duplicates start disagreeing.

Review feedback on where the region-wide coverage checks belong, and it was
right on both counts. The checks lived in the finder page's frontmatter, which
made that page the only file under src/pages/ that throws, and meant a future
map or partner page could reintroduce the unexpanded-coverage bug while the
page it was written for stayed correct. Worse, frontmatter only runs during a
full astro build, and the GitHub Actions lane runs none — so checks written to
fail closed never ran on a pull request at all.

src/data/partners/index.ts now owns them, validating as it loads and exporting
the derived selectors, the way src/data/use-cases/ and src/data/case-studies/
already do. Consumers import @data/partners, so the dataset fails for every
consumer rather than for whoever remembered to check. scripts/lint-distributors.ts
plus a ci.yml job put that in the fast lane.

Three of the four checks stopped being runtime checks. Coverage collapses into
one field — countries: [string, ...string[]] | 'region-wide' — which makes the
two states exclusive by construction and an empty list a compile error, so the
regionWide flag and its "leave countries empty" doc comment are both gone. The
non-empty tuple is what earns that: a plain string[] union would still admit [].
REGIONS becomes as const with a Region type, so a region name typo is a compile
error too, and Record<Region, string[]> pins the table's keys.

That last one closes a hole this branch shipped: nothing validated a
distributor's regions, so 'Middle east' on a region-wide entry contributed no
countries, rendered an empty data-countries, and dropped the card out of every
filter — the exact failure the checks exist to catch, passing them because the
flag was set. The ?? [] fallback that made it silent is gone; the lookup is
total now.

Both lint-script fixtures were mutation-tested, because the first version could
not fail. It passed [fixture] as the whole distributor list, so every real
country became unclassified and the stray-country branch alone satisfied the
assertion — deleting the missing-country check entirely still left it green.
Each fixture now perturbs the real data by one country and asserts the offending
name appears in the message, so disabling either half of findCoverageErrors
fails that half and only that half.

CLAUDE.md's CI list named three jobs and would have been wrong the moment this
merged; it now names four, and the distributor finder gets a section stating the
two-file rule the coverage check enforces.
rusikv and others added 2 commits August 20, 2026 15:25
Both sides appended a quality-check command to the same line of CLAUDE.md's
list — main's lint:dualrender and this branch's lint:distributors. Kept both.

lint:dualrender is deliberately not wired into any pipeline, per its own note in
the CI Checks section, so the distributors job added here is still the only new
GitHub Actions job and that section's list stays accurate.
The lint script and its GitHub Actions job cost a full checkout and pnpm
install to run a 0.10s check that needs no dependencies, on every PR and
every push, while src/data/partners/ changes a few times a year. Drop
both and let the import-time assertion carry it.

To keep that trade favourable, the one invariant the script was the only
thing catching becomes a compile error: regions is now a non-empty tuple,
so an entry with no regions no longer type-checks. It would otherwise
render a card invisible under every filter, on a green build.

The assertion also moves next to the data it constrains — distributors.ts
calls assertDistributorData from the new coverage.ts, following
assertNewestFirst in src/models/. Importing @data/partners/distributors
directly no longer bypasses validation.

Also:

- the stray-country error says "names" rather than "covers" (it compares
  names, and a region-wide distributor covers without naming), leads with
  the non-destructive remedy, and names the distributors that would lose
  coverage if you drop them
- COVERED_COUNTRIES -> OFFERED_COUNTRIES: it holds the countries
  distributors name, not everything covered
- the per-region dropdown lists move into the data module as
  REGION_OFFERED_COUNTRIES, so getNamedCountries leaves the barrel and
  getCoverage is the only per-distributor accessor consumers can reach
- populateCountries reads the server-rendered <select> instead of
  re-deriving the country union in the browser
- CLAUDE.md: correct where the guard lives, the barrel's scope, and the
  Prettier settings (120 / es5, per .prettierrc)

Dropdown contents are byte-for-byte unchanged.
@vvlladd28
vvlladd28 merged commit 34c7b1a into thingsboard:main Aug 20, 2026
4 of 5 checks passed
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.

3 participants