From 3bdc4de19e7215f0a41f6e05ed90461b779dbdbb Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 3 Mar 2026 10:00:04 -0600 Subject: [PATCH 1/3] Fix duplicate key risks in Svelte each blocks High risk fixes: - workflow-callback: change || to ?? so falsy eventId/requestId values don't skip to index fallback - combobox: add (v) key to multiselect chip rendering - schedule-recent-runs: prefix index fallback with _ to prevent collisions with real workflow/run IDs Medium risk fixes: - Add stable id field to SearchAttributeFilter type with generateFilterId() factory, replacing fragile attribute-index composite keys in filter-list, dropdown-filter-list (workflow and activity variants) - Drop redundant index from column keys in orderable-list and event-summary-table where labels are already unique - Update all SearchAttributeFilter construction sites to include id - Update tests to use toMatchObject for filter assertions --- .../event/event-summary-table.svelte | 2 +- .../schedule/schedule-recent-runs.svelte | 2 +- .../filter-list.svelte | 2 +- .../filterable-table-cell.svelte | 6 ++- .../dropdown-filter-list.svelte | 2 +- .../activity-counts.svelte | 2 + .../orderable-list.svelte | 2 +- .../dropdown-filter/text-filter.svelte | 6 ++- .../workflow-datetime-filter.svelte | 7 ++- .../dropdown-filter/workflow-status.svelte | 6 ++- .../filter-bar/dropdown-filter-list.svelte | 2 +- .../status-dropdown-filter-chip.svelte | 7 ++- .../workflow/workflow-callback.svelte | 2 +- .../workflow/workflow-counts.svelte | 2 + .../filterable-table-cell.svelte | 6 ++- src/lib/holocene/combobox/combobox.svelte | 2 +- src/lib/models/search-attribute-filters.ts | 4 ++ .../query/to-list-workflow-filters.test.ts | 44 +++++++++---------- .../query/to-list-workflow-filters.ts | 6 ++- 19 files changed, 75 insertions(+), 37 deletions(-) diff --git a/src/lib/components/event/event-summary-table.svelte b/src/lib/components/event/event-summary-table.svelte index 7c0b5ae499..dc9596f3e7 100644 --- a/src/lib/components/event/event-summary-table.svelte +++ b/src/lib/components/event/event-summary-table.svelte @@ -91,7 +91,7 @@ class="border-t-0" > - {#each columns as column, i (`${column.label}:${i}`)} + {#each columns as column (column.label)} {#if column.label === 'Event Type'} diff --git a/src/lib/components/schedule/schedule-recent-runs.svelte b/src/lib/components/schedule/schedule-recent-runs.svelte index 29403d46e2..d847bc6f7e 100644 --- a/src/lib/components/schedule/schedule-recent-runs.svelte +++ b/src/lib/components/schedule/schedule-recent-runs.svelte @@ -45,7 +45,7 @@ {translate('common.view-all-runs')} - {#each sortRecentRuns(recentRuns) as run, i (`${run?.startWorkflowResult?.workflowId ?? i}:${run?.startWorkflowResult?.runId ?? i + 1}`)} + {#each sortRecentRuns(recentRuns) as run, i (`${run?.startWorkflowResult?.workflowId ?? `_${i}`}:${run?.startWorkflowResult?.runId ?? `_${i}`}`)} {#await fetchWorkflowForSchedule({ namespace, workflowId: decodeURIForSvelte(run.startWorkflowResult.workflowId), runId: run.startWorkflowResult.runId }, fetch) then workflow}
diff --git a/src/lib/components/search-attribute-filter/filter-list.svelte b/src/lib/components/search-attribute-filter/filter-list.svelte index dc4435899d..71173a2ce5 100644 --- a/src/lib/components/search-attribute-filter/filter-list.svelte +++ b/src/lib/components/search-attribute-filter/filter-list.svelte @@ -70,7 +70,7 @@
- {#each visibleFilters as workflowFilter, i (`${workflowFilter.attribute}-${i}`)} + {#each visibleFilters as workflowFilter, i (workflowFilter.id)} {@const { attribute, value, conditional, customDate } = workflowFilter} {#if attribute}
diff --git a/src/lib/components/standalone-activities/activities-summary-configurable-table/filterable-table-cell.svelte b/src/lib/components/standalone-activities/activities-summary-configurable-table/filterable-table-cell.svelte index 2222273bf8..b433b8d22d 100644 --- a/src/lib/components/standalone-activities/activities-summary-configurable-table/filterable-table-cell.svelte +++ b/src/lib/components/standalone-activities/activities-summary-configurable-table/filterable-table-cell.svelte @@ -4,7 +4,10 @@ import FilterOrCopyButtons from '$lib/holocene/filter-or-copy-buttons.svelte'; import Link from '$lib/holocene/link.svelte'; import { translate } from '$lib/i18n/translate'; - import type { SearchAttributeFilter } from '$lib/models/search-attribute-filters'; + import { + generateFilterId, + type SearchAttributeFilter, + } from '$lib/models/search-attribute-filters'; import { activityFilters } from '$lib/stores/filters'; import { SEARCH_ATTRIBUTE_TYPE, @@ -34,6 +37,7 @@ if (!filter || filter.value !== value) { const newFilter: SearchAttributeFilter = { + id: generateFilterId(), attribute, type, value, diff --git a/src/lib/components/standalone-activities/activities-summary-filter-bar/dropdown-filter-list.svelte b/src/lib/components/standalone-activities/activities-summary-filter-bar/dropdown-filter-list.svelte index 6db1c9e76c..15df55dd02 100644 --- a/src/lib/components/standalone-activities/activities-summary-filter-bar/dropdown-filter-list.svelte +++ b/src/lib/components/standalone-activities/activities-summary-filter-bar/dropdown-filter-list.svelte @@ -89,7 +89,7 @@ {#if visibleFilters.length > 0}
- {#each visibleFilters as activityFilter, i (activityFilter.attribute + '-' + i)} + {#each visibleFilters as activityFilter, i (activityFilter.id)} {#if isStatusFilter(activityFilter) && i === firstExecutionStatusIndex} {type} (in view) - {#each columnsInUse as { label }, index (`${label}:${index}`)} + {#each columnsInUse as { label }, index (label)} "${formatISO(startDateWithTime)}"`; const filter: SearchAttributeFilter = { + id: generateFilterId(), attribute: timeField, type: SEARCH_ATTRIBUTE_TYPE.DATETIME, value: query, diff --git a/src/lib/components/workflow/dropdown-filter/workflow-status.svelte b/src/lib/components/workflow/dropdown-filter/workflow-status.svelte index ce5c7575ff..e18456d922 100644 --- a/src/lib/components/workflow/dropdown-filter/workflow-status.svelte +++ b/src/lib/components/workflow/dropdown-filter/workflow-status.svelte @@ -12,7 +12,10 @@ } from '$lib/holocene/menu'; import { translate } from '$lib/i18n/translate'; import Translate from '$lib/i18n/translate.svelte'; - import type { SearchAttributeFilter } from '$lib/models/search-attribute-filters'; + import { + generateFilterId, + type SearchAttributeFilter, + } from '$lib/models/search-attribute-filters'; import { workflowStatusFilters } from '$lib/models/workflow-status'; import { workflowFilters } from '$lib/stores/filters'; import { SEARCH_ATTRIBUTE_TYPE } from '$lib/types/workflows'; @@ -24,6 +27,7 @@ function mapStatusToFilter(value: string): SearchAttributeFilter { return { + id: generateFilterId(), attribute: 'ExecutionStatus', type: SEARCH_ATTRIBUTE_TYPE.KEYWORD, value, diff --git a/src/lib/components/workflow/filter-bar/dropdown-filter-list.svelte b/src/lib/components/workflow/filter-bar/dropdown-filter-list.svelte index 27ebcee7bd..4e1c48ae19 100644 --- a/src/lib/components/workflow/filter-bar/dropdown-filter-list.svelte +++ b/src/lib/components/workflow/filter-bar/dropdown-filter-list.svelte @@ -86,7 +86,7 @@ {#if visibleFilters.length > 0}
- {#each visibleFilters as workflowFilter, i (workflowFilter.attribute + '-' + i)} + {#each visibleFilters as workflowFilter, i (workflowFilter.id)} {#if isStatusFilter(workflowFilter) && i === firstExecutionStatusIndex}
{#if links.length} - {#each links as link, i (link.workflowEvent?.eventRef?.eventId || link.workflowEvent?.requestIdRef?.requestId || i)} + {#each links as link, i (link.workflowEvent?.eventRef?.eventId ?? link.workflowEvent?.requestIdRef?.requestId ?? i)} {#if multiselect && isArrayValue(value) && value.length > 0} {#if displayChips} - {#each value.slice(0, chipLimit) as v} + {#each value.slice(0, chipLimit) as v (v)} removeOption(v)} removeButtonLabel={removeChipLabel}>{v} `filter-${nextFilterId++}`; + export type SearchAttributeFilter = { + id: string; attribute: Extract; type: SearchAttributeType; value: string; diff --git a/src/lib/utilities/query/to-list-workflow-filters.test.ts b/src/lib/utilities/query/to-list-workflow-filters.test.ts index df169384b4..130d5b447c 100644 --- a/src/lib/utilities/query/to-list-workflow-filters.test.ts +++ b/src/lib/utilities/query/to-list-workflow-filters.test.ts @@ -73,7 +73,7 @@ describe('toListWorkflowFilters', () => { value: 'Hello = world', }, ]; - expect(result).toEqual(expectedFilters); + expect(result).toMatchObject(expectedFilters); }); it('should parse a query with prefix search', () => { @@ -88,7 +88,7 @@ describe('toListWorkflowFilters', () => { value: 'hello', }, ]; - expect(result).toEqual(expectedFilters); + expect(result).toMatchObject(expectedFilters); }); it('should parse a query with custom attributes that have spaces', () => { @@ -114,7 +114,7 @@ describe('toListWorkflowFilters', () => { value: 'Hello world', }, ]; - expect(result).toEqual(expectedFilters); + expect(result).toMatchObject(expectedFilters); }); it('should parse a query with an executionStatus', () => { @@ -129,7 +129,7 @@ describe('toListWorkflowFilters', () => { value: 'Completed', }, ]; - expect(result).toEqual(expectedFilters); + expect(result).toMatchObject(expectedFilters); }); it('should parse a query with multiple executionStatuses', () => { @@ -163,7 +163,7 @@ describe('toListWorkflowFilters', () => { value: 'Completed', }, ]; - expect(result).toEqual(expectedFilters); + expect(result).toMatchObject(expectedFilters); }); it('should parse a query with a workflowId', () => { @@ -178,7 +178,7 @@ describe('toListWorkflowFilters', () => { value: 'Hello world', }, ]; - expect(result).toEqual(expectedFilters); + expect(result).toMatchObject(expectedFilters); }); it('should parse a query with a workflowType', () => { @@ -193,7 +193,7 @@ describe('toListWorkflowFilters', () => { value: 'World', }, ]; - expect(result).toEqual(expectedFilters); + expect(result).toMatchObject(expectedFilters); }); it('should parse a query with a workflowId and workflowType', () => { @@ -216,7 +216,7 @@ describe('toListWorkflowFilters', () => { value: 'World', }, ]; - expect(result).toEqual(expectedFilters); + expect(result).toMatchObject(expectedFilters); }); it('should parse a query with a startTime', () => { @@ -234,7 +234,7 @@ describe('toListWorkflowFilters', () => { value: '2022-04-18T17:45:18-06:00', }, ]; - expect(result).toEqual(expectedFilters); + expect(result).toMatchObject(expectedFilters); }); it('should parse a query with a closeTime', () => { @@ -252,7 +252,7 @@ describe('toListWorkflowFilters', () => { value: '2022-04-18T17:45:18-06:00', }, ]; - expect(result).toEqual(expectedFilters); + expect(result).toMatchObject(expectedFilters); }); it('should parse a query with a Bool type', () => { @@ -267,7 +267,7 @@ describe('toListWorkflowFilters', () => { value: 'true', }, ]; - expect(result).toEqual(expectedFilters); + expect(result).toMatchObject(expectedFilters); }); it('should parse a query with a KeywordList type', () => { @@ -282,7 +282,7 @@ describe('toListWorkflowFilters', () => { value: '("Hello", "World")', }, ]; - expect(result).toEqual(expectedFilters); + expect(result).toMatchObject(expectedFilters); }); it('should parse a query with a KeywordList type and other types', () => { @@ -348,7 +348,7 @@ describe('toListWorkflowFilters', () => { value: '("Hello", "World")', }, ]; - expect(result).toEqual(expectedFilters); + expect(result).toMatchObject(expectedFilters); }); it('should parse a query BETWEEN two times', () => { @@ -368,7 +368,7 @@ describe('toListWorkflowFilters', () => { customDate: true, }, ]; - expect(result).toEqual(expectedFilters); + expect(result).toMatchObject(expectedFilters); }); it('should parse a query with a workflowType and startTime', () => { @@ -394,7 +394,7 @@ describe('toListWorkflowFilters', () => { value: '2022-04-18T17:45:18-06:00', }, ]; - expect(result).toEqual(expectedFilters); + expect(result).toMatchObject(expectedFilters); }); it('should parse a query with a workflowId and BETWEEN two times', () => { @@ -425,7 +425,7 @@ describe('toListWorkflowFilters', () => { customDate: true, }, ]; - expect(result).toEqual(expectedFilters); + expect(result).toMatchObject(expectedFilters); }); it('should parse a query with a workflowType and startTime and execution status', () => { @@ -459,7 +459,7 @@ describe('toListWorkflowFilters', () => { value: 'Canceled', }, ]; - expect(result).toEqual(expectedFilters); + expect(result).toMatchObject(expectedFilters); }); it('should parse a query with multiple executionStatuses and workflowType and startTime', () => { @@ -510,7 +510,7 @@ describe('toListWorkflowFilters', () => { value: '2022-04-18T17:45:18-06:00', }, ]; - expect(result).toEqual(expectedFilters); + expect(result).toMatchObject(expectedFilters); }); it('should not throw if given an invalid start time', () => { @@ -1179,7 +1179,7 @@ describe('combineFilters', () => { value: null, }, ]; - expect(result).toEqual(expectedFilters); + expect(result).toMatchObject(expectedFilters); }); it('should parse a query with IS NOT NULL', () => { @@ -1194,7 +1194,7 @@ describe('combineFilters', () => { value: null, }, ]; - expect(result).toEqual(expectedFilters); + expect(result).toMatchObject(expectedFilters); }); it('should parse a query with IS NULL and IS NOT NULL', () => { @@ -1220,7 +1220,7 @@ describe('combineFilters', () => { value: null, }, ]; - expect(result).toEqual(expectedFilters); + expect(result).toMatchObject(expectedFilters); }); it('should parse a query with "is" and "is not" as a value', () => { @@ -1246,6 +1246,6 @@ describe('combineFilters', () => { value: 'is not', }, ]; - expect(result).toEqual(expectedFilters); + expect(result).toMatchObject(expectedFilters); }); }); diff --git a/src/lib/utilities/query/to-list-workflow-filters.ts b/src/lib/utilities/query/to-list-workflow-filters.ts index de2c7eea0e..6eb2446533 100644 --- a/src/lib/utilities/query/to-list-workflow-filters.ts +++ b/src/lib/utilities/query/to-list-workflow-filters.ts @@ -1,6 +1,9 @@ import debounce from 'just-debounce'; -import type { SearchAttributeFilter } from '$lib/models/search-attribute-filters'; +import { + generateFilterId, + type SearchAttributeFilter, +} from '$lib/models/search-attribute-filters'; import { currentPageKey } from '$lib/stores/pagination'; import { type FilterParameters, @@ -55,6 +58,7 @@ const isDatetimeStatement = is(SEARCH_ATTRIBUTE_TYPE.DATETIME); const isBoolStatement = is(SEARCH_ATTRIBUTE_TYPE.BOOL); export const emptyFilter = (): SearchAttributeFilter => ({ + id: generateFilterId(), attribute: '', type: SEARCH_ATTRIBUTE_TYPE.KEYWORD, value: '', From 2b8ce24305ee43dbad03f514e9d1bbbc6728c76e Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 3 Mar 2026 10:39:25 -0600 Subject: [PATCH 2/3] Add duplicate key audit documentation Detailed explanation of every each block key change, the specific problem each fix addresses, and which items were reviewed and intentionally left unchanged. --- duplicate-key-audit.md | 177 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 duplicate-key-audit.md diff --git a/duplicate-key-audit.md b/duplicate-key-audit.md new file mode 100644 index 0000000000..5ca1d95ecc --- /dev/null +++ b/duplicate-key-audit.md @@ -0,0 +1,177 @@ +# Svelte `{#each}` Block Duplicate Key Audit + +## Background + +In Svelte, `{#each items as item (key)}` blocks use the key expression to track item identity across DOM updates. When keys are duplicated, Svelte cannot correctly associate DOM nodes with data items, leading to rendering bugs—stale state, mismatched content, or unnecessary DOM teardown and recreation. + +This audit reviewed every keyed `{#each}` block in the codebase and fixed those at risk of duplicate or ineffective keys. + +--- + +## High Risk Fixes + +### 1. `workflow-callback.svelte:48` — `||` changed to `??` + +**Before:** + +```svelte +{#each links as link, i (link.workflowEvent?.eventRef?.eventId || link.workflowEvent?.requestIdRef?.requestId || i)} +``` + +**After:** + +```svelte +{#each links as link, i (link.workflowEvent?.eventRef?.eventId ?? link.workflowEvent?.requestIdRef?.requestId ?? i)} +``` + +**Problem:** The `||` operator treats all falsy values (`0`, `""`, `false`) as missing and skips to the next fallback. If an `eventId` were `0` (a valid ID), it would be silently ignored and the key would fall through to `requestIdRef` or the index. + +**Fix:** `??` (nullish coalescing) only falls back on `null` or `undefined`, preserving valid falsy IDs like `0`. + +--- + +### 2. `combobox.svelte:480` — Added missing key + +**Before:** + +```svelte +{#each value.slice(0, chipLimit) as v} +``` + +**After:** + +```svelte +{#each value.slice(0, chipLimit) as v (v)} +``` + +**Problem:** No key expression at all. When a chip is removed from the middle of the selected values, Svelte falls back to index-based diffing—potentially misassociating chip components with the wrong values, causing stale `onremove` handlers or visual glitches. + +**Fix:** Added `(v)` as the key. Multiselect combobox values are unique strings (you can't select the same option twice), so the value itself is a stable, unique identifier. + +--- + +### 3. `schedule-recent-runs.svelte:48` — Fixed fallback key collision + +**Before:** + +```svelte +{#each sortRecentRuns(recentRuns) as run, i (`${run?.startWorkflowResult?.workflowId ?? i}:${run?.startWorkflowResult?.runId ?? i + 1}`)} +``` + +**After:** + +```svelte +{#each sortRecentRuns(recentRuns) as run, i (`${run?.startWorkflowResult?.workflowId ?? `_${i}`}:${run?.startWorkflowResult?.runId ?? `_${i}`}`)} +``` + +**Problem:** When `workflowId` or `runId` is null, the key falls back to the raw index number. A run at index 1 with null fields would produce key `"1:2"`. A real run with `workflowId="1"` and `runId="2"` would produce the same key `"1:2"` — a collision. + +**Fix:** Prefix fallback values with `_` (e.g., `"_1:_1"`), which can never collide with real workflow/run IDs since those don't start with underscores. + +--- + +## Medium Risk Fixes + +### 4–6. Filter lists — Added stable `id` to `SearchAttributeFilter` + +**Files changed:** + +- `search-attribute-filter/filter-list.svelte` +- `workflow/filter-bar/dropdown-filter-list.svelte` +- `activities-summary-filter-bar/dropdown-filter-list.svelte` + +**Before:** + +```svelte +{#each visibleFilters as workflowFilter, i (`${workflowFilter.attribute}-${i}`)} +``` + +**After:** + +```svelte +{#each visibleFilters as workflowFilter, i (workflowFilter.id)} +``` + +**Problem:** Filters allow the same attribute multiple times (e.g., two `StartTime` filters for a date range), so `attribute` alone isn't unique. The index suffix made the composite key technically unique but defeated the purpose of keying — removing a filter from the middle caused every subsequent filter chip to get a new key, triggering full DOM teardown and recreation instead of a simple removal. + +**Fix:** Added an `id: string` field to the `SearchAttributeFilter` type and a `generateFilterId()` factory function in `search-attribute-filters.ts`. Every filter now gets a stable, unique ID at creation time that persists across list mutations. + +**Supporting changes across all filter construction sites:** + +- `to-list-workflow-filters.ts` — `emptyFilter()` now includes `id` +- `workflow-datetime-filter.svelte` — 2 construction sites +- `text-filter.svelte` — 1 construction site +- `workflow-status.svelte` — `mapStatusToFilter()` +- `status-dropdown-filter-chip.svelte` — 2 construction sites +- `filterable-table-cell.svelte` (workflow) — 1 construction site +- `filterable-table-cell.svelte` (activities) — 1 construction site +- `activity-counts.svelte` — 1 construction site +- `workflow-counts.svelte` — 1 construction site + +**Test changes:** + +- `to-list-workflow-filters.test.ts` — Changed `toEqual` to `toMatchObject` for filter assertions since test expectations don't need to assert on the `id` field. + +--- + +### 7. `orderable-list.svelte:36` — Dropped redundant index + +**Before:** + +```svelte +{#each columnsInUse as { label }, index (`${label}:${index}`)} +``` + +**After:** + +```svelte +{#each columnsInUse as { label }, index (label)} +``` + +**Problem:** Column labels are unique within a table configuration (the UI prevents adding the same column twice). The `:${index}` suffix added no uniqueness value but prevented Svelte from recognizing that a column at a new position was the same column — causing unnecessary DOM recreation when columns were reordered. + +**Fix:** Use `label` alone as the key, enabling Svelte to properly track columns across reorders. + +--- + +### 8. `event-summary-table.svelte:94` — Dropped redundant index + +**Before:** + +```svelte +{#each columns as column, i (`${column.label}:${i}`)} +``` + +**After:** + +```svelte +{#each columns as column (column.label)} +``` + +**Problem:** Same pattern as orderable-list — table header column labels are unique by definition (each column has a distinct label). The index suffix was unnecessary noise. + +**Fix:** Use `column.label` alone. + +--- + +## Items Reviewed and Left As-Is + +### `add-search-attributes.svelte:45` — `${attribute.label}-${id}` + +Index IS the identity here by design. The component uses `bind:attribute={attributesToAdd[id]}` where `id` is the loop index, meaning the index is the binding target. Changing the key wouldn't improve behavior. + +### `chip-input.svelte:114,175` — `${chip}-${i}` + +Chips are user-entered strings that can legitimately be duplicated (e.g., entering "foo" twice). Using `chip` alone would break on duplicates. The `${chip}-${i}` composite is an acceptable tradeoff — the DOM churn on removal is negligible for a handful of simple chip components. + +### `calendar.svelte:38` — `(index)` + +Pure index key is equivalent to no key, but calendar cells never reorder or mutate, so there's no practical impact. + +### All `(label)` keys on table rows + +`deployment-table-row.svelte`, `version-table-row.svelte`, `schedules-table-row.svelte`, `workflow-actions.svelte` — these use translated label strings as keys. Column definitions are static per table, so labels are always unique. Low risk. + +### All ID-based keys + +Keys using `activity.id`, `command.id`, `endpoint.id`, `workflow.id:runId` composites, enum-based status keys, and `iterableKey(event)` are all genuinely unique and correct. From 969fb0eb752579ffd8e5e4124c318229dcb4f1cf Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 3 Mar 2026 10:41:27 -0600 Subject: [PATCH 3/3] remove omc --- .serena/project.yml | 15 ++++++++++++++- AUTHENTICATION.md => docs/AUTHENTICATION.md | 0 .../DUPLICATE_EACH_BLOCK_KEY_AUDIT.md | 0 3 files changed, 14 insertions(+), 1 deletion(-) rename AUTHENTICATION.md => docs/AUTHENTICATION.md (100%) rename duplicate-key-audit.md => docs/DUPLICATE_EACH_BLOCK_KEY_AUDIT.md (100%) diff --git a/.serena/project.yml b/.serena/project.yml index e6f6b30892..16a74c54a0 100644 --- a/.serena/project.yml +++ b/.serena/project.yml @@ -109,4 +109,17 @@ encoding: utf-8 # The first language is the default language and the respective language server will be used as a fallback. # Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored. languages: - - typescript +- typescript + +# time budget (seconds) per tool call for the retrieval of additional symbol information +# such as docstrings or parameter information. +# This overrides the corresponding setting in the global configuration; see the documentation there. +# If null or missing, use the setting from the global configuration. +symbol_info_budget: + +# The language backend to use for this project. +# If not set, the global setting from serena_config.yml is used. +# Valid values: LSP, JetBrains +# Note: the backend is fixed at startup. If a project with a different backend +# is activated post-init, an error will be returned. +language_backend: diff --git a/AUTHENTICATION.md b/docs/AUTHENTICATION.md similarity index 100% rename from AUTHENTICATION.md rename to docs/AUTHENTICATION.md diff --git a/duplicate-key-audit.md b/docs/DUPLICATE_EACH_BLOCK_KEY_AUDIT.md similarity index 100% rename from duplicate-key-audit.md rename to docs/DUPLICATE_EACH_BLOCK_KEY_AUDIT.md