Skip to content

SCAL-321380: Add converter from EmbedEvent.FilterChanged payload to HostEvent.UpdateFilters - #586

Open
sastaachar wants to merge 8 commits into
mainfrom
feat/scal-321380-filter-converter
Open

SCAL-321380: Add converter from EmbedEvent.FilterChanged payload to HostEvent.UpdateFilters#586
sastaachar wants to merge 8 commits into
mainfrom
feat/scal-321380-filter-converter

Conversation

@sastaachar

Copy link
Copy Markdown
Contributor

Summary

  • EmbedEvent.FilterChanged returns Liveboard filter state in a shape that's incompatible with what HostEvent.UpdateFilters expects, forcing consumers to hand-parse and reformat the payload (especially tricky for date filters) in order to reapply it later.
  • Adds convertFilterChangedToUpdateFiltersPayload(), a converter utility that bridges the two payload formats, covering liveboard filters (attribute/measure + all date filter types: EXACT_DATE, EXACT_DATE_RANGE, MONTH_YEAR, QUARTER_YEAR, YEAR_ONLY, LAST_N_PERIOD/NEXT_N_PERIOD, period-only types) and runtime filters.
  • Widens the UpdateFilters request type in contracts.ts to include columnName/operator aliases, datePeriod, negate, and includeCurrentPeriod, matching what the host app already accepts but wasn't reflected in the SDK's types.
  • Exports the new function/types from the main and React entry points.

Fixes SCAL-321380.

Test plan

  • tsc --noEmit passes with no errors
  • eslint passes on all new/changed files
  • jest -c jest.config.sdk.js src/utils/filterConverter.spec.ts — 17 new tests passing (~96% line coverage)
  • jest -c jest.config.sdk.js src/embed/hostEventClient — no regressions (75 existing tests passing)

🤖 Generated with Claude Code

@sastaachar
sastaachar requested a review from a team as a code owner July 14, 2026 08:10

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request introduces a utility function convertFilterChangedToUpdateFiltersPayload along with comprehensive unit tests and type exports to convert the payload of EmbedEvent.FilterChanged into the format expected by HostEvent.UpdateFilters. It also updates the UpdateFilters contract to support optional properties and broader value types. The feedback recommends using loose inequality checks (!= null) instead of strict inequality checks (!== undefined) when validating optional properties like epoch, number, and includeCurrentPeriod to safely handle potential null values in the incoming JSON payloads.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +83 to +110
const DATE_FILTER_VALUE_EXTRACTORS: Record<string, DateFilterValueExtractor> = {
EXACT_DATE: (dateFilter) => (
dateFilter.epoch !== undefined ? [Number(dateFilter.epoch)] : []
),
EXACT_DATE_RANGE: (dateFilter) => {
const { lowEpoch, highEpoch } = dateFilter.dateRange ?? {};
return lowEpoch !== undefined && highEpoch !== undefined
? [Number(lowEpoch), Number(highEpoch)]
: [];
},
MONTH_YEAR: (dateFilter) => (
dateFilter.monthName && dateFilter.yearName
? [dateFilter.monthName, dateFilter.yearName]
: []
),
QUARTER_YEAR: (dateFilter) => (
dateFilter.quarterName && dateFilter.yearName
? [dateFilter.quarterName, dateFilter.yearName]
: []
),
YEAR_ONLY: (dateFilter) => (dateFilter.yearName ? [dateFilter.yearName] : []),
LAST_N_PERIOD: (dateFilter) => (
dateFilter.number !== undefined ? [dateFilter.number] : []
),
NEXT_N_PERIOD: (dateFilter) => (
dateFilter.number !== undefined ? [dateFilter.number] : []
),
};

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.

medium

Using !== undefined to check for optional properties can lead to bugs if the incoming payload contains null values (which is common in JSON payloads from APIs for unset fields). For example, if epoch is null, dateFilter.epoch !== undefined evaluates to true, and Number(null) evaluates to 0, resulting in an incorrect epoch value of 0 (1970-01-01) instead of ignoring the filter. Using != null is a safer and more robust way to check that a value is neither null nor undefined.

const DATE_FILTER_VALUE_EXTRACTORS: Record<string, DateFilterValueExtractor> = {
    EXACT_DATE: (dateFilter) => (
        dateFilter.epoch != null ? [Number(dateFilter.epoch)] : []
    ),
    EXACT_DATE_RANGE: (dateFilter) => {
        const { lowEpoch, highEpoch } = dateFilter.dateRange ?? {};
        return lowEpoch != null && highEpoch != null
            ? [Number(lowEpoch), Number(highEpoch)]
            : [];
    },
    MONTH_YEAR: (dateFilter) => (
        dateFilter.monthName && dateFilter.yearName
            ? [dateFilter.monthName, dateFilter.yearName]
            : []
    ),
    QUARTER_YEAR: (dateFilter) => (
        dateFilter.quarterName && dateFilter.yearName
            ? [dateFilter.quarterName, dateFilter.yearName]
            : []
    ),
    YEAR_ONLY: (dateFilter) => (dateFilter.yearName ? [dateFilter.yearName] : []),
    LAST_N_PERIOD: (dateFilter) => (
        dateFilter.number != null ? [dateFilter.number] : []
    ),
    NEXT_N_PERIOD: (dateFilter) => (
        dateFilter.number != null ? [dateFilter.number] : []
    ),
};

Comment thread src/utils/filterConverter.ts Outdated
Comment on lines +130 to +132
if (dateFilter.includeCurrentPeriod !== undefined) {
param.includeCurrentPeriod = dateFilter.includeCurrentPeriod;
}

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.

medium

Similarly, use != null to protect against null values in the payload, which would otherwise be assigned to param.includeCurrentPeriod and violate the expected boolean | undefined type.

Suggested change
if (dateFilter.includeCurrentPeriod !== undefined) {
param.includeCurrentPeriod = dateFilter.includeCurrentPeriod;
}
if (dateFilter.includeCurrentPeriod != null) {
param.includeCurrentPeriod = dateFilter.includeCurrentPeriod;
}

sastaachar added a commit that referenced this pull request Jul 14, 2026
…filters

Address Fable review of #586:
- Convert every filterContent/dateFilterContent entry per column instead
  of only the first, so compound conditions on a single column are no
  longer silently dropped.
- Skip (rather than emit with empty values) date filters missing a
  required field (e.g. EXACT_DATE without epoch) or with an unrecognized
  type, so a partial payload can't clear/corrupt a filter on re-apply.
- Export the nested Liveboard filter types for consumer typing.
- Document the columnName/operator aliases and converter in the
  HostEvent.UpdateFilters JSDoc.
- Add tests for the above plus isValidUpdateFiltersPayload integration.

SCAL-321380
@sastaachar

Copy link
Copy Markdown
Contributor Author

Fable review

Ran a review pass with the Fable model. Findings and resolution:

  1. Silent data loss — only the first filterContent/dateFilterContent entry per filter was converted, dropping compound conditions on a single column. Fixed: now converts every entry via flatMap.
  2. Empty values on incomplete date filters — e.g. an EXACT_DATE filter missing epoch produced { values: [] }, which could clear/corrupt the filter on re-apply instead of failing loudly. Fixed: value-bearing date filter types now skip the filter entirely if required fields are missing; unrecognized date filter types are also skipped rather than guessed at.
  3. Nested Liveboard filter types weren't exported — consumers couldn't type intermediate values. Fixed: LiveboardFilterGroup, LiveboardFilter, LiveboardFilterContent, LiveboardFilterContentValue, LiveboardDateFilterContent, LiveboardDateFilterValue are now exported.
  4. HostEvent.UpdateFilters JSDoc didn't mention the new aliases/converterfixed, added a pointer to convertFilterChangedToUpdateFiltersPayload.
  5. Known limitation (documented, not fixed): multiple date-filter conditions on the same column (e.g. an OR of two date ranges) aren't guaranteed to round-trip losslessly, since HostEvent.UpdateFilters treats date filters as replace-not-merge per column server-side. Non-date (attribute/measure) filters don't have this issue — the app merges multiple entries for the same column. Noted in the function's JSDoc.
  6. Contract widening in contracts.ts (all-optional column/columnName/oper/operator) was flagged as slightly loose (a payload with empty values still typechecks) — left as-is since it matches the existing permissive runtime validation in isValidUpdateFiltersPayload, and a stricter union type risked more churn than the ticket's scope warranted.

Added 7 new test cases covering the fixes (multiple conditions per column, multiple filters per group, falsy values like 0/false, incomplete/unrecognized date filters, and an integration check against isValidUpdateFiltersPayload). All 24 tests pass, tsc/eslint clean.

🤖 Review performed with Claude Code using the Fable model.

sastaachar and others added 3 commits August 20, 2026 08:56
FilterChanged returns liveboard filter state in a different shape than
HostEvent.UpdateFilters expects, forcing consumers to hand-parse and
reformat the payload (especially for date filters) to reapply it later.
Adds convertFilterChangedToUpdateFiltersPayload() to bridge the two
formats, and widens the UpdateFilters request type to match the fields
the host app actually accepts (columnName/operator aliases, datePeriod,
negate, includeCurrentPeriod).

SCAL-321380
…filters

Address Fable review of #586:
- Convert every filterContent/dateFilterContent entry per column instead
  of only the first, so compound conditions on a single column are no
  longer silently dropped.
- Skip (rather than emit with empty values) date filters missing a
  required field (e.g. EXACT_DATE without epoch) or with an unrecognized
  type, so a partial payload can't clear/corrupt a filter on re-apply.
- Export the nested Liveboard filter types for consumer typing.
- Document the columnName/operator aliases and converter in the
  HostEvent.UpdateFilters JSDoc.
- Add tests for the above plus isValidUpdateFiltersPayload integration.

SCAL-321380
The FilterChanged payload arrives as JSON from the embedded app, so an
absent field can be null rather than omitted. The converter checked only
for undefined, so `epoch: null` coerced to `Number(null)` === 0 and
produced a valid-looking filter pinned to the Unix epoch; `number: null`
likewise produced a zero-length period. Non-numeric strings had the same
problem via NaN.

Route epoch/count fields through a toFiniteNumber() helper that rejects
null, empty string and non-finite values, and skip the filter when a
required field is missing, matching the existing behaviour for absent
fields. Genuine zeroes (epoch 0, includeCurrentPeriod false) are kept.

Also rename the converter's payload interfaces to a FilterChanged*
family: main has since added its own LiveboardFilter to the host event
contracts, and both were re-exported from the package root.

Addresses the review feedback on PR #586.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sastaachar
sastaachar force-pushed the feat/scal-321380-filter-converter branch from 5cb4d7e to 4a673b8 Compare August 20, 2026 03:44
@sastaachar

Copy link
Copy Markdown
Contributor Author

Rebased onto current main, plus the outstanding review fix

This branch was 104 commits behind main, so I've rebased it. Three things
changed in the process that are worth a reviewer's attention.

1. FilterUpdate is now widened rather than bypassed

Since this PR was opened, main added a FilterUpdate interface to
hostEventClient/contracts.ts (SCAL-298895 #579, SCAL-323273 #594). The
original PR had inlined its own expanded shape into the UpdateFilters
contract, which now conflicts.

I kept main's FilterUpdate and widened it instead:

  • column / columnName and oper / operator are both accepted, as optional aliases
  • values widened from string[] to (string | number | boolean | bigint)[]
  • added datePeriod, negate, includeCurrentPeriod
  • applicability from main is untouched

This isn't a new relaxation so much as making the type honest about what the
code already does — main's own isValidUpdateFiltersPayload() in
hostEventClient/utils.ts already validates column || columnName and
oper || operator. The type was stricter than the validator. FilterUpdate is
only referenced by the UpdateFilters contract, so nothing else is affected.

Flagging this explicitly for the SCAL-323273 / SCAL-298895 authors,
since it relaxes required fields on a type you added.

2. Converter payload types renamed to a FilterChanged* family

Main's new LiveboardFilter (host event contracts) collided with the
converter's own LiveboardFilter, and both are re-exported from the package
root — a hard TS2300: Duplicate identifier. The converter's payload
interfaces are now:

FilterChangedFilterGroup, FilterChangedFilter, FilterChangedFilterContent,
FilterChangedFilterContentValue, FilterChangedDateFilterContent,
FilterChangedDateFilterValue

These have never shipped, so there's no compatibility cost, and the names now
match the FilterChangedPayload root they hang off.

3. The Gemini null-check comment was a real bug, now fixed

The bot's !== undefined!= null note wasn't a style nit. The
FilterChanged payload is JSON from the embedded app, so an absent field can
arrive as null rather than omitted:

  • epoch: nullNumber(null) is 0 → emitted a valid-looking filter
    pinned to 1 Jan 1970
    instead of being skipped
  • number: null → a zero-length LAST_N_PERIOD / NEXT_N_PERIOD
  • non-numeric or empty strings failed the same way via NaN

Epoch and count fields now go through a toFiniteNumber() helper that rejects
null, '' and non-finite values, and the filter is skipped when a required
field is missing — consistent with the existing handling of absent fields.
Genuine zeroes are preserved: epoch: 0 and includeCurrentPeriod: false both
survive. The payload interfaces are typed | null to match the wire format.

Verification

  • filterConverter.spec.ts36/36 pass (13 new: null/empty/NaN epoch, null
    range bounds, null period count, epoch 0, numeric-string epoch, null vs
    false includeCurrentPeriod, null keys, falsy-but-real keys)
  • src/embed/hostEventClient118/118 pass, covering the FilterUpdate widening
  • tsc --noEmit and eslint clean on all changed files

This has only had bot review since 14 July — could I get a human reviewer on it?

@sastaachar sastaachar changed the title feat(filters): add converter from FilterChanged payload to UpdateFilters SCAL-321380: Add converter from EmbedEvent.FilterChanged payload to HostEvent.UpdateFilters Aug 20, 2026
@pkg-pr-new

pkg-pr-new Bot commented Aug 20, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@thoughtspot/visual-embed-sdk@586

commit: fbb7ff5

Comment thread src/utils/filterConverter.ts Outdated
Comment on lines +249 to +255
const runtimeFilterParams: UpdateFiltersFilterParam[] = (
filterChangedPayload?.runtimeFilters ?? []
).map((runtimeFilter) => ({
columnName: runtimeFilter.columnName,
operator: runtimeFilter.operator,
values: runtimeFilter.values,
}));

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.

Correctness (plausible): the runtime-filter branch copies columnName/operator/values straight through with no null-safety, unlike every other path in this file.

The whole point of 4a673b8b (this same PR) was that the FilterChanged payload is JSON from the embedded app, so an absent field can arrive as null rather than being omitted — and that a naive pass-through silently produces a corrupt-but-valid-looking filter. That fix was applied to liveboardFilters/dateFilter fields (toFiniteNumber, the isNil checks), but runtimeFilters here gets none of it. If the host ever emits a runtimeFilters entry with columnName: null (same JSON-round-trip mechanism as the rest of the payload), it flows straight into the output filters array. isValidUpdateFiltersPayload's hasColumn check (typeof f.columnName === 'string') would then fail for that one entry, and since it checks payload.filters.every(isValidFilter), the entire trigger(HostEvent.UpdateFilters, ...) call throws — not just the bad entry.

RuntimeFilter's fields are typed as required, so this may be a non-issue if the host always populates them fully for this event (unlike the liveboard-filter payload). But there's no test covering a null/missing field here, whereas the equivalent liveboard-filter and date-filter cases are extensively tested (see the null and non-numeric values in the payload describe block). Worth confirming with the host payload's actual contract, or applying the same defensive filtering used elsewhere.

@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Review verdict: Clean

No correctness, security, or public-API-breaking issues found in the diff.

What was reviewed:

  • filterConverter.ts / filterConverter.spec.ts — new convertFilterChangedToUpdateFiltersPayload utility that reshapes an EmbedEvent.FilterChanged payload into the HostEvent.UpdateFilters shape. Walked through the date-filter extractors (EXACT_DATE, EXACT_DATE_RANGE, MONTH_YEAR, QUARTER_YEAR, YEAR_ONLY, LAST_N_PERIOD/NEXT_N_PERIOD, period-only types), the toFiniteNumber null/empty-string/NaN handling, malformed-runtime-filter isolation (a bad entry is dropped instead of invalidating the whole filters array), and applicability (tab/group scope) propagation and its "skip rather than silently widen" behavior when a scope is unusable. All of this is logically sound and backed by extensive, well-targeted tests (null vs. 0/false, string-encoded epochs, blank targetId, mixed valid/invalid entries, etc.).
  • contracts.tsFilterUpdate.column/oper are now optional with columnName/operator accepted as aliases, and values is widened to (string | number | boolean | bigint)[] to match RuntimeFilter.values. Both are backward-compatible wideners (existing callers passing string[]/column/oper are unaffected) and line up with the alias-aware checks already in isValidUpdateFiltersPayload.
  • index.ts / all-types-export.ts — the new converter and its supporting types are exported consistently across both public barrels.
  • types.ts — doc-only addition to HostEvent.UpdateFilters documenting the columnName/operator aliases and pointing at the new converter. (Note: the UpdatePersonalizedView/UpdatePersonalisedView doc changes and the new @deprecated tag visible in a base-vs-HEAD diff are not part of this PR's own commits — they come from PR SCAL-330172 Deprecate the host event updatePersonalizedView #629 already merged into the target branch — so they're out of scope here.)
Style guide

No documentation issues found in the changed lines.

…through

The runtimeFilters branch copied columnName/operator/values straight
through while every other path in the converter skips entries it can't
faithfully reconstruct. That asymmetry has a blast radius:
isValidUpdateFiltersPayload rejects the entire filters array if any one
entry is missing a column, operator or values array, so a single
malformed runtime filter would make trigger(HostEvent.UpdateFilters)
throw and take every correctly converted filter down with it.

Skip such entries, matching the rest of the file. A runtime filter with
an empty values array is still valid and is kept.

Raised by the automated review on PR #586.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sastaachar

Copy link
Copy Markdown
Contributor Author

Good catch on the runtimeFilters branch — fixed in c89145df.

It was a real asymmetry: every other path in the converter skips entries it
can't faithfully reconstruct, but runtime filters were copied through verbatim.
The blast radius is what makes it worth fixing rather than leaving to the host —
isValidUpdateFiltersPayload rejects the entire filters array if any one
entry is missing a column, operator or values array. So a single malformed
runtime filter wouldn't just drop itself, it would make
trigger(HostEvent.UpdateFilters) throw and take every correctly converted
Liveboard filter down with it.

convertRuntimeFilterToParam() now skips entries without a non-empty string
columnName, a non-empty string operator, and an array values. A runtime
filter with an empty values array is still valid and is kept.

9 new tests, including the one that matters: a bad runtime filter alongside good
ones leaves the good ones intact and the resulting payload still satisfies
isValidUpdateFiltersPayload. 45/45 converter tests pass; tsc and eslint clean.

Comment on lines +52 to +56
export interface FilterChangedFilterGroup {
columnInfo?: {
name?: string;
};
filters?: FilterChangedFilter[];

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.

Correctness: applicability is dropped during conversion, silently changing where a re-applied filter scopes to.

src/types.ts's docs for EmbedEvent.FilterChanged (~line 3386) and HostEvent.GetFilters (~line 5728) both state that a Liveboard filter entry carries an optional applicability ({ level: 'LIVEBOARD' | 'TAB' | 'GROUP', targetId? }) since SDK 1.53.0. HostEvent.UpdateFilters's FilterUpdate (contracts.ts) already accepts applicability on each filter.

FilterChangedFilterGroup/FilterChangedFilter here have no applicability field, and neither convertFilterGroupToParams (line 227) nor convertFilterToParams (line 200) read or propagate one onto UpdateFiltersFilterParam.

Concrete failure: a filter scoped to a specific tab (applicability: { level: 'TAB', targetId: 'abc' }) is captured via convertFilterChangedToUpdateFiltersPayload(payload) and later replayed with liveboardEmbed.trigger(HostEvent.UpdateFilters, savedFilters). The scoping is silently lost — the filter re-applies at the Liveboard level instead of the original tab, with no error to indicate the state wasn't faithfully restored. That directly undermines the stated purpose of this utility ("capture and re-apply later").

Comment on lines +36 to +47
/**
* Name of the column to filter on. `columnName` is an accepted alias, so a
* payload produced by `convertFilterChangedToUpdateFiltersPayload` can be
* passed straight through.
*/
column?: string;
columnName?: string;
/**
* Operator to apply. `operator` is an accepted alias.
*/
oper?: string;
operator?: string;

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.

Doc comments here are published to developers.thoughtspot.com, and TypeDoc attaches a leading comment only to the declaration immediately below it — so columnName and operator (the new alias properties) will render with no description at all, while column/oper get one. Suggest giving the aliases their own (even one-line) doc comments.

Suggested change
/**
* Name of the column to filter on. `columnName` is an accepted alias, so a
* payload produced by `convertFilterChangedToUpdateFiltersPayload` can be
* passed straight through.
*/
column?: string;
columnName?: string;
/**
* Operator to apply. `operator` is an accepted alias.
*/
oper?: string;
operator?: string;
/**
* Name of the column to filter on. `columnName` is an accepted alias, so a
* payload produced by `convertFilterChangedToUpdateFiltersPayload` can be
* passed straight through.
*/
column?: string;
/**
* Alias for `column`.
*/
columnName?: string;
/**
* Operator to apply. `operator` is an accepted alias.
*/
oper?: string;
/**
* Alias for `oper`.
*/
operator?: string;

Comment thread src/types.ts
Comment on lines 5875 to +5878
* }
* });
* ```
* `columnName` and `operator` are also accepted as aliases for `column`

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.

Tag-ordering (style guide rule 1): this new paragraph about the columnName/operator aliases and convertFilterChangedToUpdateFiltersPayload is inserted after all the @example blocks but before @version. Per the canonical order, free-text description belongs with the short description at the top of the comment, not sandwiched between @example and @version. Consider moving it up to the main description instead.

sastaachar and others added 4 commits August 20, 2026 17:19
liveboardFilters entries carry an optional applicability ({ level,
targetId }) scoping a filter to a LIVEBOARD, TAB or GROUP, added in SDK
1.53.0 / 26.10.0.cl, and HostEvent.UpdateFilters accepts the same field
per filter. The converter dropped it, so a filter scoped to one tab was
replayed with no scope at all - which UpdateFilters treats as LIVEBOARD
level. Restoring saved filter state would silently widen a tab filter
across the whole Liveboard and change what every other tab shows.

Carry the group's applicability onto every param it produces. When a
scope is present but unusable - unknown level, or TAB/GROUP without a
targetId - skip the filter instead: widening is the worse failure, and
isValidUpdateFiltersPayload would reject the entire filters array over
one malformed entry, taking every other filter with it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
isValidUpdateFiltersPayload accepts columnName/operator as aliases for
column/oper, but handleUpdateFiltersEvent forwards the payload to the
embedded app verbatim and only column/oper are known to be understood
there. A payload using the aliases therefore passed validation and was
then silently ignored downstream - the worst of both, since it looked
accepted.

Canonicalise each filter to column/oper after validation, so either
spelling works end to end. The canonical spelling wins when both are
present.

Also narrow the FilterUpdate contract to what is actually known to be
supported: columnName/operator are declared and column/oper marked
@deprecated in favour of them, matching RuntimeFilter and the
FilterChanged payload, and values widens to
(string | number | boolean | bigint)[] to match RuntimeFilter.values.
datePeriod, negate and includeCurrentPeriod are deliberately NOT
declared - the converter emits them, but nothing verifies the embedded
app honours them, and declaring a field in a public contract asserts
support we do not have evidence for.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ten public exports for one function was too much surface for a published
package. FilterChangedFilterGroup, FilterChangedFilter,
FilterChangedFilterContent, FilterChangedFilterContentValue,
FilterChangedDateFilterContent and FilterChangedDateFilterValue describe
the internal structure of a payload callers receive and pass straight
through - naming them is not something a consumer needs to do.

They stay exported from the module for internal use and tests, just not
re-exported from the package root. What remains public is the function
plus the three types a caller actually touches: FilterChangedPayload in,
UpdateFiltersPayload out, and UpdateFiltersFilterParam for iterating it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
"Canonicalise" was jargon for a plain operation, and British spelling in
a codebase that uses American. The names now say what happens:

  canonicaliseFilter          -> resolveFilterAliases
  canonicaliseUpdateFiltersPayload -> resolveUpdateFiltersAliases

Doc comments reworded to match. No behaviour change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

1 participant