From 9334727d99eac251b0a995916c7ea00bd9596cef Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Fri, 14 Aug 2026 13:31:26 +0200 Subject: [PATCH 01/60] feat: stream audit logs in batches when a page is slow to load (#10695) * feat: stream audit logs in batches when a page is slow to load Co-Authored-By: Claude Opus 5 (1M context) * fix: bound streamed page size and clear stale rows on stop Co-Authored-By: Claude Opus 5 (1M context) * fix: keep the runs batch cap and drop rows of a replaced query on failure Co-Authored-By: Claude Opus 5 (1M context) * fix: ignore stop once a load has settled and reset paging when one fails Co-Authored-By: Claude Opus 5 (1M context) * chore: update ee-repo-ref to aab7da6e1f8b1fadacc2208913a5d6596f06f922 This commit updates the EE repository reference after PR #727 was merged in windmill-ee-private. Previous ee-repo-ref: 59ba8d7ce9ce1de0814b159b3813c2ac2a49239a New ee-repo-ref: aab7da6e1f8b1fadacc2208913a5d6596f06f922 Automated by sync-ee-ref workflow. --------- Co-authored-by: Claude Opus 5 (1M context) Co-authored-by: windmill-internal-app[bot] --- backend/ee-repo-ref.txt | 2 +- backend/windmill-api/openapi.yaml | 9 + backend/windmill-audit/src/lib.rs | 3 + .../lib/components/BatchLoadProgress.svelte | 62 +++++ frontend/src/lib/components/RunsPage.svelte | 40 +-- .../auditLogs/AuditLogsFilters.svelte | 86 ++---- .../auditLogs/AuditLogsTable.svelte | 23 +- .../auditLogs/useAuditLogsLoader.svelte.ts | 246 ++++++++++++++++++ .../auditLogs/useAuditLogsLoader.test.ts | 19 ++ .../(root)/(logged)/audit_logs/+page.svelte | 62 ++++- 10 files changed, 438 insertions(+), 114 deletions(-) create mode 100644 frontend/src/lib/components/BatchLoadProgress.svelte create mode 100644 frontend/src/lib/components/auditLogs/useAuditLogsLoader.svelte.ts create mode 100644 frontend/src/lib/components/auditLogs/useAuditLogsLoader.test.ts diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 6f6c280541..de456c2426 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -a65162b22b127b54c0686095ee1b16b04e3111f7 +aab7da6e1f8b1fadacc2208913a5d6596f06f922 diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index c61c092c95..df46ec1ec8 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -268,6 +268,15 @@ paths: type: string - $ref: "#/components/parameters/ResourceName" - $ref: "#/components/parameters/ActionKind" + - name: before_id + in: query + description: > + only return logs with an id strictly lower than this one. Logs are ordered by + descending id, so this is a keyset cursor to stream a page in several batches + without paying a growing offset. + schema: + type: integer + format: int64 - name: all_workspaces in: query description: get audit logs for all workspaces diff --git a/backend/windmill-audit/src/lib.rs b/backend/windmill-audit/src/lib.rs index 8e74edfbf0..f1443fdb18 100644 --- a/backend/windmill-audit/src/lib.rs +++ b/backend/windmill-audit/src/lib.rs @@ -37,5 +37,8 @@ pub struct ListAuditLogQuery { pub resource: Option, pub before: Option>, pub after: Option>, + // Keyset cursor on the `id DESC` ordering. Lets a client stream a page in small batches + // without paying a growing OFFSET on every batch. + pub before_id: Option, pub all_workspaces: Option, } diff --git a/frontend/src/lib/components/BatchLoadProgress.svelte b/frontend/src/lib/components/BatchLoadProgress.svelte new file mode 100644 index 0000000000..16cf2d7212 --- /dev/null +++ b/frontend/src/lib/components/BatchLoadProgress.svelte @@ -0,0 +1,62 @@ + + +
+ Loading {itemsLabel}: {loaded} of {total}... +
+
+
+ {#if batchSize != null} + Batch size: + { + const v = parseInt(e.currentTarget.value) + if (v >= 1 && v <= maxBatchSize) { + onBatchSizeChange?.(v) + } else { + e.currentTarget.value = String(batchSize) + } + } + }} + /> + {/if} + +
diff --git a/frontend/src/lib/components/RunsPage.svelte b/frontend/src/lib/components/RunsPage.svelte index d775c1d212..03b410d232 100644 --- a/frontend/src/lib/components/RunsPage.svelte +++ b/frontend/src/lib/components/RunsPage.svelte @@ -48,6 +48,7 @@ import { twMerge } from 'tailwind-merge' import { computeJobKinds, useJobsLoader } from '$lib/components/runs/useJobsLoader.svelte' import ConcurrentJobsChart from '$lib/components/ConcurrentJobsChart.svelte' + import BatchLoadProgress from '$lib/components/BatchLoadProgress.svelte' import { pluralize, MAX_RESOLUTION_BATCH, MAX_RESOLUTION_NOTE_LEN } from '$lib/utils' import BatchReRunOptionsPane, { type BatchReRunOptions @@ -934,35 +935,16 @@
{#if batchProgress} -
- Loading jobs: {batchProgress.loaded} of {batchProgress.total}... -
-
-
- {#if currentBatchSize != null} - Batch size: - { - const v = parseInt(e.currentTarget.value) - if (v >= 1 && v <= 1000) { - jobsLoader.restreamWithBatchSize(v) - } - }} - /> - {/if} - +
+ jobsLoader.restreamWithBatchSize(v)} + onStop={() => jobsLoader.stopBatchLoading()} + />
{/if} diff --git a/frontend/src/lib/components/auditLogs/AuditLogsFilters.svelte b/frontend/src/lib/components/auditLogs/AuditLogsFilters.svelte index c367bce01c..6bf71c65fd 100644 --- a/frontend/src/lib/components/auditLogs/AuditLogsFilters.svelte +++ b/frontend/src/lib/components/auditLogs/AuditLogsFilters.svelte @@ -21,24 +21,21 @@ import CalendarPicker from '$lib/components/common/calendarPicker/CalendarPicker.svelte' import { type AuditLog, - AuditService, ResourceService, UserService, ScriptService, FlowService, - AppService, - CancelError + AppService } from '$lib/gen' import { userStore, workspaceStore } from '$lib/stores' import { ChevronDown, Download, Loader2, RefreshCcw } from 'lucide-svelte' - import { onDestroy, untrack } from 'svelte' + import { onDestroy, onMount, untrack } from 'svelte' import ToggleButtonGroup from '../common/toggleButton-v2/ToggleButtonGroup.svelte' import ToggleButton from '../common/toggleButton-v2/ToggleButton.svelte' import Select from '../select/Select.svelte' import { usePromise } from '$lib/svelte5Utils.svelte' import { safeSelectItems } from '../select/utils.svelte' - import { CancelablePromiseUtils } from '$lib/cancelable-promise-utils' import { sendUserToast } from '$lib/toast' let usernames: string[] | undefined = $state() @@ -48,7 +45,6 @@ logs?: AuditLog[] username?: string pageIndex?: number | undefined - hasMore?: boolean before?: string | undefined after?: string | undefined perPage?: number | undefined @@ -57,13 +53,13 @@ actionKind?: ActionKind | 'all' scope?: undefined | 'all_workspaces' | 'instance' loading?: boolean + onRefresh?: () => void } let { - logs = $bindable(undefined), + logs = undefined, username = $bindable('all'), pageIndex = $bindable(1), - hasMore = $bindable(false), before = $bindable(undefined), after = $bindable(undefined), perPage = $bindable(100), @@ -71,13 +67,11 @@ resource = $bindable() as string | undefined, actionKind = $bindable(undefined), scope = $bindable(undefined), - loading = $bindable(false) + loading = false, + onRefresh }: Props = $props() $effect.pre(() => { - if (logs == undefined) { - logs = [] - } if (operation == undefined) { operation = 'all' } @@ -89,47 +83,6 @@ } }) - function loadLogs() { - loading = true - - let username_ = username == 'all' ? undefined : username - let operation_ = operation == 'all' || operation == '' ? undefined : operation - let actionKind_ = actionKind == 'all' ? undefined : actionKind - let resource_ = resource == 'all' || resource == '' ? undefined : resource - - let _promise = AuditService.listAuditLogs({ - workspace: scope === 'instance' ? 'global' : $workspaceStore!, - page: pageIndex, - perPage, - before, - after, - username: username_, - operation: operation_, - resource: resource_, - actionKind: actionKind_, - allWorkspaces: scope === 'all_workspaces' - }) - let promise = CancelablePromiseUtils.map(_promise, (value) => { - logs = value - hasMore = !logs || (logs.length > 0 && logs.length === perPage) - loading = false - }) - promise = CancelablePromiseUtils.onTimeout(promise, 4000, () => { - sendUserToast( - 'Loading audit logs is taking longer than expected...', - 'warning', - perPage > 25 - ? [{ label: 'Reduce to 25 items per page', callback: () => (perPage = 25) }] - : [] - ) - }) - promise = CancelablePromiseUtils.catchErr(promise, (e) => { - if (e instanceof CancelError) return CancelablePromiseUtils.pure(undefined) - return CancelablePromiseUtils.err(e) - }) - return promise - } - async function loadUsers() { usernames = $userStore?.is_admin || $userStore?.is_super_admin @@ -277,9 +230,6 @@ WORKSPACES_DELETE: 'workspaces.delete' } - let refresh = $state(0) - let lastRefresh = $state(-1) - function downloadAuditLogsAsJson() { if (!logs || logs.length === 0) { sendUserToast('No audit logs to download', true) @@ -302,19 +252,15 @@ URL.revokeObjectURL(url) } - // observe all the variables that should trigger an update + onMount(() => { + loadUsers() + resources.refresh() + }) + + // observe all the variables that should be reflected in the url $effect(() => { - ;[refresh, username, perPage, before, after, operation, resource, actionKind, scope, pageIndex] - return untrack(() => { - if (refresh !== lastRefresh) { - loadUsers() - resources.refresh() - lastRefresh = refresh - } - updateQueryParams() - let promise = loadLogs() - return () => promise?.cancel() - }) + ;[username, perPage, before, after, operation, resource, actionKind, scope, pageIndex] + untrack(() => updateQueryParams()) }) @@ -476,7 +422,9 @@
+ {#if batchProgress} +
+ onBatchSizeChange?.(size)} + onStop={() => onStopLoading?.()} + /> +
+ {/if}
Per page: - - +
{#if status === 'idle'} - + {#if noOAuth} +
{server.name} did not advertise OAuth support.
+ {/if} + {:else if status === 'discovering'} -
Discovering OAuth settings...
+
Checking what {server.name} supports...
{:else if status === 'discovered' && discoveryResult} -
- ✓ OAuth supported - {#if discoveryResult.supports_dynamic_registration} - (Dynamic Client Registration available) - {/if} -
- {#if discoveryResult.scopes_supported && discoveryResult.scopes_supported.length > 0} -
+ {#if resources} + {@const nTruncated = resources.filter((r) => r.truncated).length} + {#if nTruncated > 0} + +
+ {nTruncated} of those resources {nTruncated === 1 ? 'is' : 'are'} too large to search in full + — only {nTruncated === 1 ? 'its' : 'their'} beginning is matched. +
+ {/if} + {/if}
@@ -269,6 +285,15 @@ on:close > {#snippet actions()} + {#if item.truncated} + + Truncated + {#snippet text()} + This resource is too large to search in full: only its beginning is matched + and shown. + {/snippet} + + {/if} From 64d78b4db1d7d939c598c86d1998218d52fbcc21 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Mon, 17 Aug 2026 10:45:25 +0200 Subject: [PATCH 29/60] fix: fall back to polling when a proxy mutes the job SSE stream (#10716) * fix: fall back to polling when a proxy mutes the job SSE stream * fix: do not charge deliberate no-logs sse restarts to the retry budget --- frontend/src/lib/components/JobLoader.svelte | 48 +++++++++++++++----- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/frontend/src/lib/components/JobLoader.svelte b/frontend/src/lib/components/JobLoader.svelte index ec9e4b693c..71bd2fbb51 100644 --- a/frontend/src/lib/components/JobLoader.svelte +++ b/frontend/src/lib/components/JobLoader.svelte @@ -87,6 +87,7 @@ let finished: string[] = [] let ITERATIONS_BEFORE_SLOW_REFRESH = 10 let ITERATIONS_BEFORE_SUPER_SLOW_REFRESH = 100 + const MAX_SSE_ATTEMPTS = 3 let lastStartedAt: number = Date.now() let currentId: string | undefined = $state(undefined) @@ -179,7 +180,7 @@ lastCompletedJobId = undefined clearCurrentJob() lastCallbacks = callbacks - noPingTimeout = undefined + clearNoPingTimeout() const startedAt = Date.now() const testId = await fn() @@ -669,16 +670,30 @@ } } - function setNoPingTimeout(id: string, attempt: number, callbacks?: Callbacks) { + function clearNoPingTimeout() { if (noPingTimeout) { clearTimeout(noPingTimeout) + noPingTimeout = undefined } + } + + function setNoPingTimeout(id: string, attempt: number, callbacks?: Callbacks) { + clearNoPingTimeout() if (isCurrentJob(id)) { noPingTimeout = setTimeout(() => { + noPingTimeout = undefined if (isCurrentJob(id)) { currentEventSource?.close() currentEventSource = undefined - loadTestJobWithSSE(id, attempt + 1, callbacks) + // A proxy that buffers the response rather than cutting it keeps the + // connection open and error-free, so this watchdog is the only signal that + // no event is getting through. It has to share the retry budget: otherwise + // it reopens an equally mute stream forever and polling is never reached. + if (attempt < MAX_SSE_ATTEMPTS) { + loadTestJobWithSSE(id, attempt + 1, callbacks) + } else { + syncer(id, callbacks) + } } }, 10000) } @@ -841,10 +856,7 @@ if (previewJobUpdates.completed) { currentEventSource?.close() currentEventSource = undefined - if (noPingTimeout) { - clearTimeout(noPingTimeout) - noPingTimeout = undefined - } + clearNoPingTimeout() isCompleted = true if (onlyResult) { callbacks?.doneResult?.({ @@ -869,16 +881,26 @@ console.warn('SSE error:', error) currentEventSource?.close() currentEventSource = undefined + clearNoPingTimeout() let delay = 1000 let isNoLogsChange = error.type == noLogsChangeRestartEvent if (isNoLogsChange) { delay = 0 } - if (attempt < 3 || isNoLogsChange) { + if (attempt < MAX_SSE_ATTEMPTS || isNoLogsChange) { if (!isNoLogsChange) { - console.log(`SSE error (1), retrying ... attempt: ${attempt + 1}/3`) + console.log( + `SSE error (1), retrying ... attempt: ${attempt + 1}/${MAX_SSE_ATTEMPTS}` + ) } - setTimeout(() => loadTestJobWithSSE(id, attempt + 1, callbacks), delay) + // A no-logs restart is deliberate (the caller wants a stream with different + // query args), not a failure, so it must not consume the retry budget: + // toggling the flow graph tab would otherwise exhaust it in a few clicks + // and strand a healthy stream on polling. + setTimeout( + () => loadTestJobWithSSE(id, isNoLogsChange ? attempt : attempt + 1, callbacks), + delay + ) } else { // Fall back to polling on error setTimeout(() => syncer(id, callbacks), 1000) @@ -901,9 +923,10 @@ // Fall back to polling on error currentEventSource?.close() currentEventSource = undefined + clearNoPingTimeout() - if (attempt < 3) { - console.log(`SSE error (2), retrying ... attempt: ${attempt}/3`) + if (attempt < MAX_SSE_ATTEMPTS) { + console.log(`SSE error (2), retrying ... attempt: ${attempt}/${MAX_SSE_ATTEMPTS}`) attempt++ loadTestJobWithSSE(id, attempt, callbacks) } else { @@ -942,6 +965,7 @@ clearCurrentId() currentEventSource?.close() currentEventSource = undefined + clearNoPingTimeout() replayTimeouts.forEach(clearTimeout) replayTimeouts = [] }) From 010d67e07f2f036e808507e394a9a9fd2ee720ce Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Mon, 17 Aug 2026 11:08:47 +0200 Subject: [PATCH 30/60] chore(main): release 1.790.1 (#10712) * chore(main): release 1.790.1 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 13 ++ backend/Cargo.lock | 158 +++++++++--------- backend/Cargo.toml | 4 +- .../parsers/windmill-parser-wasm/Cargo.lock | 48 +++--- .../parsers/windmill-parser-wasm/Cargo.toml | 2 +- backend/windmill-api/openapi.yaml | 2 +- benchmarks/lib.ts | 2 +- cli/src/core/constants.ts | 2 +- frontend/package-lock.json | 4 +- frontend/package.json | 2 +- lsp/Pipfile | 2 +- openflow.openapi.yaml | 2 +- .../WindmillClient/WindmillClient.psd1 | 2 +- python-client/wmill/pyproject.toml | 2 +- typescript-client/jsr.json | 2 +- typescript-client/package.json | 2 +- version.txt | 2 +- windmill-yaml-validator/package-lock.json | 4 +- windmill-yaml-validator/package.json | 2 +- 20 files changed, 136 insertions(+), 123 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index c1824f4c07..deefa1024a 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.790.0" + ".": "1.790.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 40089082af..fae181899c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## [1.790.1](https://github.com/windmill-labs/windmill/compare/v1.790.0...v1.790.1) (2026-08-17) + + +### Bug Fixes + +* fall back to polling when a proxy mutes the job SSE stream ([#10716](https://github.com/windmill-labs/windmill/issues/10716)) ([64d78b4](https://github.com/windmill-labs/windmill/commit/64d78b4db1d7d939c598c86d1998218d52fbcc21)) + + +### Performance Improvements + +* cap resource content sent to the search modal ([#10714](https://github.com/windmill-labs/windmill/issues/10714)) ([529e960](https://github.com/windmill-labs/windmill/commit/529e9606297ee0b41456a66222f31409d7bc7669)) +* unblock workers before the API router is built ([#10711](https://github.com/windmill-labs/windmill/issues/10711)) ([0258f3f](https://github.com/windmill-labs/windmill/commit/0258f3f81b96bb8d4e343ba8aeba614f9c836579)) + ## [1.790.0](https://github.com/windmill-labs/windmill/compare/v1.789.0...v1.790.0) (2026-08-15) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index eec483d9e1..dd9885761d 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -14665,7 +14665,7 @@ dependencies = [ [[package]] name = "windmill" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "async-nats", @@ -14750,7 +14750,7 @@ dependencies = [ [[package]] name = "windmill-ai" -version = "1.790.0" +version = "1.790.1" dependencies = [ "async-stream", "async-trait", @@ -14783,7 +14783,7 @@ dependencies = [ [[package]] name = "windmill-alerting" -version = "1.790.0" +version = "1.790.1" dependencies = [ "axum 0.8.9", "chrono", @@ -14796,7 +14796,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "argon2", @@ -14936,7 +14936,7 @@ dependencies = [ [[package]] name = "windmill-api-agent-workers" -version = "1.790.0" +version = "1.790.1" dependencies = [ "axum 0.8.9", "chrono", @@ -14959,7 +14959,7 @@ dependencies = [ [[package]] name = "windmill-api-assets" -version = "1.790.0" +version = "1.790.1" dependencies = [ "axum 0.8.9", "chrono", @@ -14976,7 +14976,7 @@ dependencies = [ [[package]] name = "windmill-api-auth" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "axum 0.8.9", @@ -15002,7 +15002,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.790.0" +version = "1.790.1" dependencies = [ "reqwest 0.12.28", "serde", @@ -15012,7 +15012,7 @@ dependencies = [ [[package]] name = "windmill-api-configs" -version = "1.790.0" +version = "1.790.1" dependencies = [ "axum 0.8.9", "chrono", @@ -15029,7 +15029,7 @@ dependencies = [ [[package]] name = "windmill-api-debug" -version = "1.790.0" +version = "1.790.1" dependencies = [ "axum 0.8.9", "base64 0.22.1", @@ -15051,7 +15051,7 @@ dependencies = [ [[package]] name = "windmill-api-embeddings" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "axum 0.8.9", @@ -15074,7 +15074,7 @@ dependencies = [ [[package]] name = "windmill-api-flow-conversations" -version = "1.790.0" +version = "1.790.1" dependencies = [ "axum 0.8.9", "chrono", @@ -15090,7 +15090,7 @@ dependencies = [ [[package]] name = "windmill-api-flows" -version = "1.790.0" +version = "1.790.1" dependencies = [ "axum 0.8.9", "chrono", @@ -15112,7 +15112,7 @@ dependencies = [ [[package]] name = "windmill-api-groups" -version = "1.790.0" +version = "1.790.1" dependencies = [ "axum 0.8.9", "chrono", @@ -15133,7 +15133,7 @@ dependencies = [ [[package]] name = "windmill-api-inputs" -version = "1.790.0" +version = "1.790.1" dependencies = [ "axum 0.8.9", "chrono", @@ -15147,7 +15147,7 @@ dependencies = [ [[package]] name = "windmill-api-integration-tests" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "async-nats", @@ -15182,7 +15182,7 @@ dependencies = [ [[package]] name = "windmill-api-jobs" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "axum 0.8.9", @@ -15207,7 +15207,7 @@ dependencies = [ [[package]] name = "windmill-api-npm-proxy" -version = "1.790.0" +version = "1.790.1" dependencies = [ "axum 0.8.9", "chrono", @@ -15235,7 +15235,7 @@ dependencies = [ [[package]] name = "windmill-api-openapi" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "axum 0.8.9", @@ -15257,7 +15257,7 @@ dependencies = [ [[package]] name = "windmill-api-schedule" -version = "1.790.0" +version = "1.790.1" dependencies = [ "axum 0.8.9", "chrono", @@ -15277,7 +15277,7 @@ dependencies = [ [[package]] name = "windmill-api-scripts" -version = "1.790.0" +version = "1.790.1" dependencies = [ "axum 0.8.9", "chrono", @@ -15315,7 +15315,7 @@ dependencies = [ [[package]] name = "windmill-api-settings" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "axum 0.8.9", @@ -15343,7 +15343,7 @@ dependencies = [ [[package]] name = "windmill-api-sse" -version = "1.790.0" +version = "1.790.1" dependencies = [ "lazy_static", "serde", @@ -15355,7 +15355,7 @@ dependencies = [ [[package]] name = "windmill-api-users" -version = "1.790.0" +version = "1.790.1" dependencies = [ "argon2", "axum 0.8.9", @@ -15379,7 +15379,7 @@ dependencies = [ [[package]] name = "windmill-api-workers" -version = "1.790.0" +version = "1.790.1" dependencies = [ "axum 0.8.9", "chrono", @@ -15393,7 +15393,7 @@ dependencies = [ [[package]] name = "windmill-api-workspaces" -version = "1.790.0" +version = "1.790.1" dependencies = [ "axum 0.8.9", "chrono", @@ -15428,7 +15428,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.790.0" +version = "1.790.1" dependencies = [ "chrono", "lazy_static", @@ -15442,7 +15442,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "axum 0.8.9", @@ -15461,7 +15461,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.790.0" +version = "1.790.1" dependencies = [ "aes-gcm", "aho-corasick", @@ -15565,7 +15565,7 @@ dependencies = [ [[package]] name = "windmill-dep-map" -version = "1.790.0" +version = "1.790.1" dependencies = [ "chrono", "itertools 0.14.0", @@ -15584,7 +15584,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.790.0" +version = "1.790.1" dependencies = [ "regex", "serde", @@ -15599,7 +15599,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "astral-tokio-tar", @@ -15623,7 +15623,7 @@ dependencies = [ [[package]] name = "windmill-jseval" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "futures", @@ -15640,7 +15640,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.790.0" +version = "1.790.1" dependencies = [ "itertools 0.14.0", "lazy_static", @@ -15656,7 +15656,7 @@ dependencies = [ [[package]] name = "windmill-mcp" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "async-trait", @@ -15677,7 +15677,7 @@ dependencies = [ [[package]] name = "windmill-native-triggers" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "async-trait", @@ -15708,7 +15708,7 @@ dependencies = [ [[package]] name = "windmill-oauth" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "arc-swap", @@ -15733,7 +15733,7 @@ dependencies = [ [[package]] name = "windmill-object-store" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "async-stream", @@ -15767,7 +15767,7 @@ dependencies = [ [[package]] name = "windmill-operator" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "futures", @@ -15785,7 +15785,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.790.0" +version = "1.790.1" dependencies = [ "convert_case 0.6.0", "serde", @@ -15794,7 +15794,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "lazy_static", @@ -15806,7 +15806,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "serde_json", @@ -15818,7 +15818,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "gosyn", @@ -15830,7 +15830,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "lazy_static", @@ -15842,7 +15842,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "serde_json", @@ -15854,7 +15854,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "nu-parser", @@ -15865,7 +15865,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15876,7 +15876,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15888,7 +15888,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-asset" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "rustpython-ast", @@ -15899,7 +15899,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "async-recursion", @@ -15921,7 +15921,7 @@ dependencies = [ [[package]] name = "windmill-parser-r" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "serde_json", @@ -15933,7 +15933,7 @@ dependencies = [ [[package]] name = "windmill-parser-ruby" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "lazy_static", @@ -15947,7 +15947,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -15964,7 +15964,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "lazy_static", @@ -15977,7 +15977,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql-asset" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "serde", @@ -15989,7 +15989,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "lazy_static", @@ -16007,7 +16007,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts-asset" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "serde-wasm-bindgen", @@ -16023,7 +16023,7 @@ dependencies = [ [[package]] name = "windmill-parser-wac" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "rustpython-ast", @@ -16039,7 +16039,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "lazy_static", @@ -16053,7 +16053,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "async-recursion", @@ -16092,7 +16092,7 @@ dependencies = [ [[package]] name = "windmill-runtime-nativets" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "const_format", @@ -16132,7 +16132,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.790.0" +version = "1.790.1" dependencies = [ "getrandom 0.3.4", "wasm-bindgen", @@ -16143,7 +16143,7 @@ dependencies = [ [[package]] name = "windmill-store" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "async-recursion", @@ -16178,7 +16178,7 @@ dependencies = [ [[package]] name = "windmill-test-utils" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "async-trait", @@ -16202,7 +16202,7 @@ dependencies = [ [[package]] name = "windmill-trigger" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "async-trait", @@ -16235,7 +16235,7 @@ dependencies = [ [[package]] name = "windmill-trigger-amqp" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "async-trait", @@ -16262,7 +16262,7 @@ dependencies = [ [[package]] name = "windmill-trigger-azure" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "async-trait", @@ -16295,7 +16295,7 @@ dependencies = [ [[package]] name = "windmill-trigger-email" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "async-trait", @@ -16315,7 +16315,7 @@ dependencies = [ [[package]] name = "windmill-trigger-gcp" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "async-trait", @@ -16349,7 +16349,7 @@ dependencies = [ [[package]] name = "windmill-trigger-http" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "async-trait", @@ -16385,7 +16385,7 @@ dependencies = [ [[package]] name = "windmill-trigger-kafka" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "async-trait", @@ -16408,7 +16408,7 @@ dependencies = [ [[package]] name = "windmill-trigger-mqtt" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "async-trait", @@ -16432,7 +16432,7 @@ dependencies = [ [[package]] name = "windmill-trigger-nats" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "async-nats", @@ -16456,7 +16456,7 @@ dependencies = [ [[package]] name = "windmill-trigger-postgres" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "async-trait", @@ -16491,7 +16491,7 @@ dependencies = [ [[package]] name = "windmill-trigger-sqs" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "async-trait", @@ -16519,7 +16519,7 @@ dependencies = [ [[package]] name = "windmill-trigger-websocket" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "async-trait", @@ -16544,7 +16544,7 @@ dependencies = [ [[package]] name = "windmill-types" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "bitflags 2.13.1", @@ -16563,7 +16563,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "async-once-cell", @@ -16679,7 +16679,7 @@ dependencies = [ [[package]] name = "windmill-worker-volumes" -version = "1.790.0" +version = "1.790.1" dependencies = [ "bytes", "futures", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index f19861b16e..9905d35af5 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.790.0" +version = "1.790.1" authors.workspace = true edition.workspace = true @@ -88,7 +88,7 @@ members = [ exclude = ["./windmill-duckdb-ffi-internal", "./parsers/windmill-parser-wasm"] [workspace.package] -version = "1.790.0" +version = "1.790.1" authors = ["Ruben Fiszel "] edition = "2021" diff --git a/backend/parsers/windmill-parser-wasm/Cargo.lock b/backend/parsers/windmill-parser-wasm/Cargo.lock index 9866121d20..5eb3df3fcc 100644 --- a/backend/parsers/windmill-parser-wasm/Cargo.lock +++ b/backend/parsers/windmill-parser-wasm/Cargo.lock @@ -6191,7 +6191,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windmill-common" -version = "1.790.0" +version = "1.790.1" dependencies = [ "aho-corasick", "anyhow", @@ -6274,7 +6274,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.790.0" +version = "1.790.1" dependencies = [ "proc-macro2", "quote", @@ -6286,7 +6286,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.790.0" +version = "1.790.1" dependencies = [ "convert_case", "serde", @@ -6295,7 +6295,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "lazy_static", @@ -6307,7 +6307,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "serde_json", @@ -6319,7 +6319,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "gosyn", @@ -6331,7 +6331,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "lazy_static", @@ -6343,7 +6343,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "serde_json", @@ -6355,7 +6355,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "nu-parser", @@ -6366,7 +6366,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "itertools 0.14.0", @@ -6377,7 +6377,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "itertools 0.14.0", @@ -6389,7 +6389,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-asset" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "rustpython-ast", @@ -6400,7 +6400,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "async-recursion", @@ -6422,7 +6422,7 @@ dependencies = [ [[package]] name = "windmill-parser-r" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "serde_json", @@ -6434,7 +6434,7 @@ dependencies = [ [[package]] name = "windmill-parser-ruby" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "lazy_static", @@ -6448,7 +6448,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "convert_case", @@ -6465,7 +6465,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "lazy_static", @@ -6478,7 +6478,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql-asset" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "serde", @@ -6490,7 +6490,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "lazy_static", @@ -6508,7 +6508,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts-asset" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "serde-wasm-bindgen", @@ -6524,7 +6524,7 @@ dependencies = [ [[package]] name = "windmill-parser-wac" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "rustpython-ast", @@ -6540,7 +6540,7 @@ dependencies = [ [[package]] name = "windmill-parser-wasm" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "getrandom 0.2.17", @@ -6572,7 +6572,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "lazy_static", @@ -6586,7 +6586,7 @@ dependencies = [ [[package]] name = "windmill-types" -version = "1.790.0" +version = "1.790.1" dependencies = [ "anyhow", "bitflags", diff --git a/backend/parsers/windmill-parser-wasm/Cargo.toml b/backend/parsers/windmill-parser-wasm/Cargo.toml index 10cace3b33..2e76d34920 100644 --- a/backend/parsers/windmill-parser-wasm/Cargo.toml +++ b/backend/parsers/windmill-parser-wasm/Cargo.toml @@ -12,7 +12,7 @@ resolver = "2" members = ["."] [workspace.package] -version = "1.790.0" +version = "1.790.1" edition = "2021" authors = ["Ruben Fiszel "] diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index ec6f722cdf..e7cad13be4 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.790.0 + version: 1.790.1 title: Windmill API contact: diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index 0bd4c7b7cf..8319d03899 100644 --- a/benchmarks/lib.ts +++ b/benchmarks/lib.ts @@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts"; import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts"; import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts"; -export const VERSION = "v1.790.0"; +export const VERSION = "v1.790.1"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/src/core/constants.ts b/cli/src/core/constants.ts index 1c69b06149..784c6f95a7 100644 --- a/cli/src/core/constants.ts +++ b/cli/src/core/constants.ts @@ -10,4 +10,4 @@ export const WM_FORK_PREFIX = "wm-fork"; // (e.g. utils.ts) can read it without importing main.ts and creating a circular // dependency (main → workspace → utils → main) that triggers a TDZ. // Re-exported from main.ts for backwards compatibility. -export const VERSION = "1.790.0"; +export const VERSION = "1.790.1"; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 89ac3d10a6..6eecdc3d5a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "@windmill-labs/components", - "version": "1.790.0", + "version": "1.790.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@windmill-labs/components", - "version": "1.790.0", + "version": "1.790.1", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index 924ad3b285..0a8873c35a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "@windmill-labs/components", - "version": "1.790.0", + "version": "1.790.1", "scripts": { "dev": "vite dev", "dev:ui-builder": "mv static/ui_builder static/ui_builder.dev-disabled 2>/dev/null || true ; trap 'mv static/ui_builder.dev-disabled static/ui_builder 2>/dev/null || true' EXIT ; vite dev", diff --git a/lsp/Pipfile b/lsp/Pipfile index 61a941b2d4..e9b02f2495 100644 --- a/lsp/Pipfile +++ b/lsp/Pipfile @@ -4,7 +4,7 @@ verify_ssl = true name = "pypi" [packages] -wmill = ">=1.790.0" +wmill = ">=1.790.1" sendgrid = "*" mysql-connector-python = "*" pymongo = "*" diff --git a/openflow.openapi.yaml b/openflow.openapi.yaml index f34ccf89c4..3608240c2c 100644 --- a/openflow.openapi.yaml +++ b/openflow.openapi.yaml @@ -1,7 +1,7 @@ openapi: '3.0.3' info: - version: 1.790.0 + version: 1.790.1 title: OpenFlow Spec contact: name: Ruben Fiszel diff --git a/powershell-client/WindmillClient/WindmillClient.psd1 b/powershell-client/WindmillClient/WindmillClient.psd1 index 12bedba8ab..60e8ea7b54 100644 --- a/powershell-client/WindmillClient/WindmillClient.psd1 +++ b/powershell-client/WindmillClient/WindmillClient.psd1 @@ -12,7 +12,7 @@ RootModule = 'WindmillClient.psm1' # Version number of this module. - ModuleVersion = '1.790.0' + ModuleVersion = '1.790.1' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/python-client/wmill/pyproject.toml b/python-client/wmill/pyproject.toml index c8049b1786..998c454d0e 100644 --- a/python-client/wmill/pyproject.toml +++ b/python-client/wmill/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill" -version = "1.790.0" +version = "1.790.1" description = "A client library for accessing Windmill server wrapping the Windmill client API" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/typescript-client/jsr.json b/typescript-client/jsr.json index 9374384495..7e0927e66d 100644 --- a/typescript-client/jsr.json +++ b/typescript-client/jsr.json @@ -1,6 +1,6 @@ { "name": "@windmill/windmill", - "version": "1.790.0", + "version": "1.790.1", "exports": "./src/index.ts", "publish": { "exclude": ["!src", "./s3Types.ts", "./sqlUtils.ts", "./client.ts", "./wacError.ts"] diff --git a/typescript-client/package.json b/typescript-client/package.json index ac6ce61c9f..1f40861c21 100644 --- a/typescript-client/package.json +++ b/typescript-client/package.json @@ -1,7 +1,7 @@ { "name": "windmill-client", "description": "Windmill SDK client for browsers and Node.js", - "version": "1.790.0", + "version": "1.790.1", "author": "Ruben Fiszel", "license": "Apache 2.0", "homepage": "https://github.com/windmill-labs/windmill/tree/main/typescript-client#readme", diff --git a/version.txt b/version.txt index 9d16d96806..4bd8559201 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.790.0 +1.790.1 diff --git a/windmill-yaml-validator/package-lock.json b/windmill-yaml-validator/package-lock.json index 110f8fe51d..2f1abcdff9 100644 --- a/windmill-yaml-validator/package-lock.json +++ b/windmill-yaml-validator/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-yaml-validator", - "version": "1.790.0", + "version": "1.790.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-yaml-validator", - "version": "1.790.0", + "version": "1.790.1", "license": "Apache 2.0", "dependencies": { "@stoplight/yaml": "^4.3.0", diff --git a/windmill-yaml-validator/package.json b/windmill-yaml-validator/package.json index 4b8f77485c..123dbf2980 100644 --- a/windmill-yaml-validator/package.json +++ b/windmill-yaml-validator/package.json @@ -1,6 +1,6 @@ { "name": "windmill-yaml-validator", - "version": "1.790.0", + "version": "1.790.1", "description": "YAML validator for Windmill flow, schedule, and trigger files", "main": "dist/index.js", "types": "dist/index.d.ts", From ab3c0206d7e9b32676d99ed0cd8c9d8939122584 Mon Sep 17 00:00:00 2001 From: Pyra <92104930+pyranota@users.noreply.github.com> Date: Mon, 17 Aug 2026 12:30:25 +0200 Subject: [PATCH 31/60] fix: support @typechecked decorator in Python relative imports (#8495) WindmillFinder's ModuleSpec lacked origin, so __file__ was never set on loaded modules. inspect.getfile() then raised "is a built-in module", breaking typeguard's @typechecked and anything else that introspects module source. Use spec_from_file_location() which sets origin correctly. Co-authored-by: Claude Opus 4.6 Co-authored-by: hugocasa --- backend/tests/fixtures/typechecked_python.sql | 20 +++++++++ backend/tests/python_jobs.rs | 44 +++++++++++++++++++ backend/windmill-worker/loader.py | 5 ++- 3 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 backend/tests/fixtures/typechecked_python.sql diff --git a/backend/tests/fixtures/typechecked_python.sql b/backend/tests/fixtures/typechecked_python.sql new file mode 100644 index 0000000000..e6350c22f3 --- /dev/null +++ b/backend/tests/fixtures/typechecked_python.sql @@ -0,0 +1,20 @@ +INSERT INTO public.script(workspace_id, created_by, content, schema, summary, description, path, hash, language, lock) VALUES ( +'test-workspace', +'test-user', +' +import inspect +import sys + +def greet(name: str) -> str: + # Verify that __file__ is set on this module (same check typeguard does) + mod = sys.modules[__name__] + source_file = inspect.getfile(mod) + return f"Hello, {name}! from {source_file}" + +def main(): + return greet("World") +', +'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}', +'', +'', +'f/system/typechecked_helper', 12349, 'python3', ''); diff --git a/backend/tests/python_jobs.rs b/backend/tests/python_jobs.rs index eae163d675..b7aa671b21 100644 --- a/backend/tests/python_jobs.rs +++ b/backend/tests/python_jobs.rs @@ -1241,6 +1241,50 @@ async fn test_python_wac_v2_with_preprocessor(db: Pool) -> anyhow::Res Ok(()) } +#[cfg(feature = "python")] +#[sqlx::test(fixtures("base", "typechecked_python"))] +async fn test_typechecked_decorator_python(db: Pool) -> anyhow::Result<()> { + initialize_tracing().await; + let server = ApiServer::start(db.clone()).await?; + let port = server.addr.port(); + + let content = r#" +from f.system.typechecked_helper import greet + +def main(): + return greet("World") +"# + .to_owned(); + + let job = JobPayload::Code(RawCode { + hash: None, + content, + path: Some("f/system/test_typechecked".to_string()), + language: ScriptLang::Python3, + lock: None, + concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default() + .into(), + debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(), + cache_ttl: None, + cache_ignore_s3_path: None, + dedicated_worker: None, + modules: None, + tag: None, + }); + + let result = run_job_in_new_worker_until_complete(&db, false, job, port) + .await + .json_result() + .unwrap(); + + let result_str = result.as_str().unwrap(); + assert!( + result_str.starts_with("Hello, World! from "), + "unexpected result: {result_str}" + ); + Ok(()) +} + /// End-to-end comparison between the legacy `step()` suspend-and-replay path /// and the new SDK inline-persist fast path, toggled per-job via the /// `WM_WAC_INLINE_FAST_PATH` env var which the Python script sets on its own diff --git a/backend/windmill-worker/loader.py b/backend/windmill-worker/loader.py index d3dc7b8a66..ab10c36b86 100644 --- a/backend/windmill-worker/loader.py +++ b/backend/windmill-worker/loader.py @@ -2,6 +2,7 @@ import sys import os from importlib.abc import MetaPathFinder, Loader from importlib.machinery import ModuleSpec, SourceFileLoader +from importlib.util import spec_from_file_location import time # Injected by backend: maps script path -> temp storage hash so preview jobs @@ -38,7 +39,7 @@ class WindmillFinder(MetaPathFinder): fullpath = folder + "/" + splitted[-1] + ".py" if os.path.exists(fullpath): - return ModuleSpec(name, SourceFileLoader(name, fullpath)) + return spec_from_file_location(name, fullpath) import urllib.parse @@ -70,7 +71,7 @@ class WindmillFinder(MetaPathFinder): return ModuleSpec(name, WindmillLoader(name)) with open(fullpath, "w+") as f: f.write(r) - return ModuleSpec(name, SourceFileLoader(name, fullpath)) + return spec_from_file_location(name, fullpath) except urllib.error.HTTPError as e: duration = time.time() - req_start if e.code != 404: From 66e3790da433eda955699caf20d988cb20d7ee7f Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Mon, 17 Aug 2026 18:27:37 +0200 Subject: [PATCH 32/60] docs: announce we are not seeking outside contribution (#10724) * docs: announce we are not seeking outside contribution Co-Authored-By: Claude Opus 5 (1M context) * docs: point big ideas at the feature request template Co-Authored-By: Claude Opus 5 (1M context) --------- Co-authored-by: Claude Opus 5 (1M context) --- .github/PULL_REQUEST_TEMPLATE.md | 14 +++++++++++++ .github/workflows/sign-cla.yml | 8 +++++++- CONTRIBUTING.md | 34 ++++++++++++++++++++++++++++++++ README.md | 9 ++++++++- 4 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 CONTRIBUTING.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..0e022ec801 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,14 @@ + + +## What does this PR do? + +## Related issue diff --git a/.github/workflows/sign-cla.yml b/.github/workflows/sign-cla.yml index 67822542a9..52329b6119 100644 --- a/.github/workflows/sign-cla.yml +++ b/.github/workflows/sign-cla.yml @@ -21,9 +21,15 @@ jobs: PERSONAL_ACCESS_TOKEN: ${{ secrets.CLA_PAT }} with: path-to-signatures: "signatures/cla.json" - path-to-document: "https://github.com/windmill-labs/windmill/blob/master/CLA.md" + path-to-document: "https://github.com/windmill-labs/windmill/blob/main/CLA.md" branch: "signatures" allowlist: rubenfiszel,bot* + custom-notsigned-prcomment: | + Thank you for taking the time to open this PR. + + Please note that **we are not seeking outside contribution at this time**. Small, trivially-verified PRs that fix a problem are still accepted, but low-value PRs (e.g. typo fixes) and PRs longer than a dozen or so lines will be closed. If you have a bigger idea, please open a [feature request](https://github.com/windmill-labs/windmill/issues/new?template=feature_request.md) instead. See [CONTRIBUTING.md](https://github.com/windmill-labs/windmill/blob/main/CONTRIBUTING.md) for the full policy. + + If your PR falls within that scope, we ask that you sign our [Contributor License Agreement](https://github.com/windmill-labs/windmill/blob/main/CLA.md) before we can accept it. You can sign the CLA by just posting a Pull Request Comment same as the below format. #below are the optional inputs - If the optional inputs are not given, then default values will be taken #remote-organization-name: enter the remote organization name where the signatures should be stored (Default is storing the signatures in the same repository) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..e3bc91fd77 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,34 @@ +# Contributing to Windmill + +At this time, we are not seeking outside contribution. + +AI has made writing code easy. The hard part, today, is not writing the code, but reviewing it, +making sure quality stays high, and keeping the product coherent. In that light, unfortunately, +external code contributions are "donating" the easy part of the job, while creating more of the +hard work. + +With that said, we are happy to accept small, trivially-verified PRs that fix a problem. However, +we ask that you refrain from submitting low-value PRs (e.g. typo fixes) or PRs that are more than a +dozen or so lines. Such PRs will be closed with a reference to this guideline. + +If you have a big idea you'd like us to consider, feel free to open a +[feature request](https://github.com/windmill-labs/windmill/issues/new?template=feature_request.md) +about it. + +This policy may change in the future as the project matures. Until then, thank you for your +understanding. + +## What is still very welcome + +- [Bug reports](https://github.com/windmill-labs/windmill/issues/new?template=bug_report.yml), with + clear reproduction steps. +- [Feature requests](https://github.com/windmill-labs/windmill/issues/new?template=feature_request.md), + including for ideas too big to be a PR. +- Questions and feedback on [Discord](https://discord.gg/V7PM2YHsPB). +- Contributions to the [Windmill Hub](https://hub.windmill.dev), where scripts, flows and apps are + shared with the community. + +## If you do open a PR + +Small, self-contained fixes are still accepted. They require signing the +[CLA](./CLA.md), which the CLA bot will prompt for on your first PR. diff --git a/README.md b/README.md index d075becce1..71d4d39a19 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Scripts are turned into sharable UIs automatically, and can be composed together

- Try it - Website - Docs - Discord - Hub - Contributor's guide + Try it - Website - Docs - Discord - Hub - Contributing

# Windmill - Developer platform for APIs, background jobs, workflows and UIs @@ -62,6 +62,7 @@ https://github.com/user-attachments/assets/d80de1d9-64de-4d89-aacd-6df23fa81fc4 - [Run a local dev setup](#run-a-local-dev-setup) - [Frontend only](#frontend-only) - [Backend + Frontend](#backend--frontend) + - [Contributing](#contributing) - [Contributors](#contributors) - [Copyright](#copyright) @@ -329,6 +330,12 @@ running options. 2. You can specify any feature flag you want to enable, for example `cargo run --features python` to enable the python executor. 7. Windmill should be available at `http://localhost:3000` +## Contributing + +At this time, we are not seeking outside contribution. Bug reports and feature requests remain very +welcome, and small, trivially-verified PRs that fix a problem are still accepted. See +[CONTRIBUTING.md](./CONTRIBUTING.md) for the full policy. + ## Contributors From 05eba6c9ab078cdedc87f197549dbdbc4b360fe3 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Mon, 17 Aug 2026 21:11:19 +0200 Subject: [PATCH 33/60] fix: include delete_after_secs in script deploy payload (#10731) --- frontend/src/lib/components/ScriptBuilder.svelte | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/lib/components/ScriptBuilder.svelte b/frontend/src/lib/components/ScriptBuilder.svelte index 9ce8c17022..5c6cb3e5a8 100644 --- a/frontend/src/lib/components/ScriptBuilder.svelte +++ b/frontend/src/lib/components/ScriptBuilder.svelte @@ -700,6 +700,7 @@ ws_error_handler_muted: script.ws_error_handler_muted, priority: script.priority, restart_unless_cancelled: script.restart_unless_cancelled, + delete_after_secs: script.delete_after_secs, timeout: script.timeout, concurrency_key: emptyString(script.concurrency_key) ? undefined : script.concurrency_key, visible_to_runner_only: script.visible_to_runner_only, From 66bffaa60d48f992e56e459efb813b24e3942610 Mon Sep 17 00:00:00 2001 From: Guilhem Date: Mon, 17 Aug 2026 21:14:11 +0200 Subject: [PATCH 34/60] feat: add empty state cards to list pages (#10726) * feat: add empty state cards to list pages Co-Authored-By: Claude Opus 5 (1M context) * fix: animate trigger drawers on first open Co-Authored-By: Claude Opus 5 (1M context) * fix: distinguish filtered-empty schedules, reuse the rAF helper Co-Authored-By: Claude Opus 5 (1M context) * fix: hide the header create button while the empty state offers it Co-Authored-By: Claude Opus 5 (1M context) * Revert "fix: hide the header create button while the empty state offers it" This reverts commit 98c57eede34710c7d8f4342831ff650dbc0665f1. Co-Authored-By: Claude Opus 5 (1M context) * fix: use the default variant for the empty state button Co-Authored-By: Claude Opus 5 (1M context) * refactor: share hasActiveFilters from the filter searchbar module Co-Authored-By: Claude Opus 5 (1M context) --------- Co-authored-by: Claude Opus 5 (1M context) --- .../src/lib/components/FilterSearchbar.svelte | 11 + .../common/emptyState/EmptyState.svelte | 49 ++ frontend/src/lib/components/common/index.ts | 2 + .../lib/components/common/tabs/TabFade.svelte | 26 + .../chat/CreatedResourceActionDrawers.svelte | 15 +- .../triggers/amqp/AmqpTriggerEditor.svelte | 6 +- .../triggers/azure/AzureTriggerEditor.svelte | 6 +- .../triggers/email/EmailTriggerEditor.svelte | 6 +- .../triggers/gcp/GcpTriggerEditor.svelte | 6 +- .../triggers/http/RouteEditor.svelte | 6 +- .../triggers/kafka/KafkaTriggerEditor.svelte | 6 +- .../triggers/mqtt/MqttTriggerEditor.svelte | 6 +- .../triggers/nats/NatsTriggerEditor.svelte | 6 +- .../postgres/PostgresTriggerEditor.svelte | 6 +- .../triggers/schedules/ScheduleEditor.svelte | 6 +- .../triggers/sqs/SqsTriggerEditor.svelte | 6 +- .../triggers/webhook/WebhookEditor.svelte | 5 +- .../websocket/WebsocketTriggerEditor.svelte | 6 +- frontend/src/lib/utils/paint.ts | 32 + .../(logged)/amqp_triggers/+page.svelte | 15 +- .../(logged)/azure_triggers/+page.svelte | 15 +- .../(logged)/email_triggers/+page.svelte | 17 +- .../(root)/(logged)/folders/+page.svelte | 313 +++---- .../(root)/(logged)/gcp_triggers/+page.svelte | 15 +- .../(logged)/kafka_triggers/+page.svelte | 15 +- .../(logged)/mqtt_triggers/+page.svelte | 15 +- .../[service_name]/+page.svelte | 26 +- .../(logged)/nats_triggers/+page.svelte | 15 +- .../(logged)/postgres_triggers/+page.svelte | 15 +- .../(root)/(logged)/resources/+page.svelte | 781 ++++++++++-------- .../(root)/(logged)/routes/+page.svelte | 15 +- .../(root)/(logged)/schedules/+page.svelte | 28 +- .../(root)/(logged)/sqs_triggers/+page.svelte | 15 +- .../(root)/(logged)/variables/+page.svelte | 615 +++++++------- .../(logged)/websocket_triggers/+page.svelte | 15 +- 35 files changed, 1261 insertions(+), 881 deletions(-) create mode 100644 frontend/src/lib/components/common/emptyState/EmptyState.svelte create mode 100644 frontend/src/lib/components/common/tabs/TabFade.svelte create mode 100644 frontend/src/lib/utils/paint.ts diff --git a/frontend/src/lib/components/FilterSearchbar.svelte b/frontend/src/lib/components/FilterSearchbar.svelte index 537e76ceff..5078e0a83b 100644 --- a/frontend/src/lib/components/FilterSearchbar.svelte +++ b/frontend/src/lib/components/FilterSearchbar.svelte @@ -54,6 +54,17 @@ ? T['options'][number]['value'] | `!${T['options'][number]['value']}` : T['options'][number]['value'] + /** + * Whether any filter currently narrows the result set — for pages that fetch + * server-side and so can't tell an empty workspace from an over-narrow filter. + * + * `false` does not count: a boolean filter that is off narrows nothing, and + * treating it as active makes an empty workspace look filtered. + */ + export function hasActiveFilters(val: Record): boolean { + return Object.values(val).some((v) => v !== undefined && v !== null && v !== '' && v !== false) + } + /** * Converts a FilterSchemaRec to a Zod schema for validation */ diff --git a/frontend/src/lib/components/common/emptyState/EmptyState.svelte b/frontend/src/lib/components/common/emptyState/EmptyState.svelte new file mode 100644 index 0000000000..8715065ad0 --- /dev/null +++ b/frontend/src/lib/components/common/emptyState/EmptyState.svelte @@ -0,0 +1,49 @@ + + +
+
+ +
+
+
{title}
+ {#if description} +
{description}
+ {/if} +
+ {#if action} + + + {/if} + {@render children?.()} +
diff --git a/frontend/src/lib/components/common/index.ts b/frontend/src/lib/components/common/index.ts index 4f390cfe57..531036c3de 100644 --- a/frontend/src/lib/components/common/index.ts +++ b/frontend/src/lib/components/common/index.ts @@ -7,6 +7,7 @@ export { default as UndoRedo } from './button/UndoRedo.svelte' export { default as NameIdTooltip } from './tooltip/NameIdTooltip.svelte' export { default as ClearableInput } from './clearableInput/ClearableInput.svelte' export { default as Drawer } from './drawer/Drawer.svelte' +export { default as EmptyState } from './emptyState/EmptyState.svelte' export { default as DrawerContent } from './drawer/DrawerContent.svelte' export { default as Kbd } from './kbd/Kbd.svelte' export { default as Menu } from './menu/Menu.svelte' @@ -15,6 +16,7 @@ export { default as SecondsInput } from './seconds/SecondsInput.svelte' export { default as Skeleton } from './skeleton/Skeleton.svelte' export { default as Tab } from './tabs/Tab.svelte' export { default as TabContent } from './tabs/TabContent.svelte' +export { default as TabFade } from './tabs/TabFade.svelte' export { default as Tabs } from './tabs/Tabs.svelte' export { default as Breadcrumb } from './breadcrumb/Breadcrumb.svelte' export { default as FileInput } from './fileInput/FileInput.svelte' diff --git a/frontend/src/lib/components/common/tabs/TabFade.svelte b/frontend/src/lib/components/common/tabs/TabFade.svelte new file mode 100644 index 0000000000..a1d8b1a621 --- /dev/null +++ b/frontend/src/lib/components/common/tabs/TabFade.svelte @@ -0,0 +1,26 @@ + + + +
*]:col-start-1 [&>*]:row-start-1', clazz)}> + {#key key} + +
+ {@render children()} +
+ {/key} +
diff --git a/frontend/src/lib/components/copilot/chat/CreatedResourceActionDrawers.svelte b/frontend/src/lib/components/copilot/chat/CreatedResourceActionDrawers.svelte index e25df2cc40..efd37403e9 100644 --- a/frontend/src/lib/components/copilot/chat/CreatedResourceActionDrawers.svelte +++ b/frontend/src/lib/components/copilot/chat/CreatedResourceActionDrawers.svelte @@ -1,6 +1,7 @@ +{#snippet newFolderPopover( + label: string, + placement: 'bottom' | 'bottom-end', + variant: 'accent' | 'default' +)} + + {#snippet trigger()} + + {/snippet} + {#snippet content({ close })} + handleKeyUp(e, () => close())} + placeholder="New folder name" + bind:value={newFolderName} + /> + +
+ +
+ {/snippet} +
+{/snippet} + @@ -131,164 +168,136 @@ New folder {:else} - - {#snippet trigger()} - - {/snippet} - {#snippet content({ close })} - handleKeyUp(e, () => close())} - placeholder="New folder name" - bind:value={newFolderName} - /> - -
- -
- {/snippet} -
+ {@render newFolderPopover('New folder', 'bottom-end', 'accent')} {/if}
- - - - Name - Labels - Scripts - Flows - Apps - Schedules - Variables - Resources - Participants - - - - - {#if folders === undefined} - {#each new Array(4) as _} - - - - - - {/each} - {:else} - {#if folders.length === 0} - - -
- No folders yet, create one -
-
- - {/if} + {#if folders?.length === 0} + + {#if !restricted} + {@render newFolderPopover('Add a folder', 'bottom', 'default')} + {/if} + + {:else} + + + + Name + Labels + Scripts + Flows + Apps + Schedules + Variables + Resources + Participants + + + + + {#if folders === undefined} + {#each new Array(4) as _} + + + + + + {/each} + {:else} + {#each folders as { name, extra_perms, owners, canWrite, summary, labels } (name)} + { + editFolderName = name + folderDrawer?.openDrawer() + }} + > + + {name} + {#if summary} +
+ {summary} + {/if} +
+ + {#if labels?.length} +
+ {#each labels.slice(0, 3) as label} + {label} + {/each} + {#if labels.length > 3} + 'Label: ' + l) + .join('\n')}>+{labels.length - 3} + {/if} +
+ {/if} +
+ - {#each folders as { name, extra_perms, owners, canWrite, summary, labels } (name)} - { - editFolderName = name - folderDrawer?.openDrawer() - }} - > - - {name} - {#if summary} -
- {summary} - {/if} -
- - {#if labels?.length} -
- {#each labels.slice(0, 3) as label} - {label} - {/each} - {#if labels.length > 3} - 'Label: ' + l) - .join('\n')}>+{labels.length - 3} - {/if} -
- {/if} -
- - - - - { - editFolderName = name - folderDrawer?.openDrawer() - } - }, - { - displayName: 'Publish to Hub', - icon: UploadCloud, - disabled: !($userStore?.is_admin || $userStore?.is_super_admin), - action: () => { - publishFolderName = name - hubDrawer?.openDrawer() - } - }, - { - displayName: `Delete${canWrite ? '' : ' (require owner permissions)'}`, - icon: Trash, - type: 'delete', - disabled: !canWrite, - action: async () => { - try { - await FolderService.deleteFolder({ - workspace: $workspaceStore ?? '', - name - }) - folders = folders?.filter((f) => f.name !== name) - } catch (e) { - sendUserToast(e.body, true) - loadFolders() + + + { + editFolderName = name + folderDrawer?.openDrawer() + } + }, + { + displayName: 'Publish to Hub', + icon: UploadCloud, + disabled: !($userStore?.is_admin || $userStore?.is_super_admin), + action: () => { + publishFolderName = name + hubDrawer?.openDrawer() + } + }, + { + displayName: `Delete${canWrite ? '' : ' (require owner permissions)'}`, + icon: Trash, + type: 'delete', + disabled: !canWrite, + action: async () => { + try { + await FolderService.deleteFolder({ + workspace: $workspaceStore ?? '', + name + }) + folders = folders?.filter((f) => f.name !== name) + } catch (e) { + sendUserToast(e.body, true) + loadFolders() + } } } - } - ]} - /> - -
- {/each} - {/if} - -
+ ]} + /> +
+ + {/each} + {/if} + +
+ {/if}
{/if} diff --git a/frontend/src/routes/(root)/(logged)/gcp_triggers/+page.svelte b/frontend/src/routes/(root)/(logged)/gcp_triggers/+page.svelte index 21442cfdba..22234e0f04 100644 --- a/frontend/src/routes/(root)/(logged)/gcp_triggers/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/gcp_triggers/+page.svelte @@ -22,7 +22,7 @@ import { base } from '$app/paths' import { page } from '$app/stores' import CenteredPage from '$lib/components/CenteredPage.svelte' - import { Alert, Badge, Button, Skeleton } from '$lib/components/common' + import { Alert, Badge, Button, EmptyState, Skeleton } from '$lib/components/common' import Dropdown from '$lib/components/DropdownV2.svelte' import PageHeader from '$lib/components/PageHeader.svelte' import SharedBadge from '$lib/components/SharedBadge.svelte' @@ -383,7 +383,18 @@ {/each} {:else if !triggers?.length} -
No GCP Pub/Sub triggers
+ gcpTriggerEditor?.openNew(false), + aiId: 'gcp-triggers-empty-add', + aiDescription: 'Add GCP Pub/Sub trigger' + }} + /> {:else if items?.length}
{#each items.slice(0, nbDisplayed) as { gcp_resource_path, topic_id, workspace_id, delivery_type, path, edited_by, error, edited_at, script_path, is_flow, extra_perms, canWrite, mode, server_id, subscription_id, retry, error_handler_path, error_handler_args, labels, draft_only, is_draft } (path)} diff --git a/frontend/src/routes/(root)/(logged)/kafka_triggers/+page.svelte b/frontend/src/routes/(root)/(logged)/kafka_triggers/+page.svelte index 44cea41b51..6ee790818a 100644 --- a/frontend/src/routes/(root)/(logged)/kafka_triggers/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/kafka_triggers/+page.svelte @@ -21,7 +21,7 @@ import { base } from '$app/paths' import { page } from '$app/stores' import CenteredPage from '$lib/components/CenteredPage.svelte' - import { Alert, Badge, Button, Skeleton } from '$lib/components/common' + import { Alert, Badge, Button, EmptyState, Skeleton } from '$lib/components/common' import Dropdown from '$lib/components/DropdownV2.svelte' import PageHeader from '$lib/components/PageHeader.svelte' import SharedBadge from '$lib/components/SharedBadge.svelte' @@ -348,7 +348,18 @@ {/each} {:else if !triggers?.length} -
No Kafka triggers
+ kafkaTriggerEditor?.openNew(false), + aiId: 'kafka-triggers-empty-add', + aiDescription: 'Add Kafka trigger' + }} + /> {:else if items?.length}
{#each items.slice(0, nbDisplayed) as { path, edited_by, edited_at, script_path, is_flow, kafka_resource_path, topics, extra_perms, canWrite, marked, server_id, error, last_server_ping, mode, retry, error_handler_path, error_handler_args, labels, draft_only, is_draft } (path)} diff --git a/frontend/src/routes/(root)/(logged)/mqtt_triggers/+page.svelte b/frontend/src/routes/(root)/(logged)/mqtt_triggers/+page.svelte index 905d5b914f..8a6d84fe90 100644 --- a/frontend/src/routes/(root)/(logged)/mqtt_triggers/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/mqtt_triggers/+page.svelte @@ -22,7 +22,7 @@ import { base } from '$app/paths' import { page } from '$app/stores' import CenteredPage from '$lib/components/CenteredPage.svelte' - import { Alert, Badge, Button, Skeleton } from '$lib/components/common' + import { Alert, Badge, Button, EmptyState, Skeleton } from '$lib/components/common' import Dropdown from '$lib/components/DropdownV2.svelte' import PageHeader from '$lib/components/PageHeader.svelte' import SharedBadge from '$lib/components/SharedBadge.svelte' @@ -338,7 +338,18 @@ {/each} {:else if !triggers?.length} -
No MQTT triggers
+ mqttTriggerEditor?.openNew(false), + aiId: 'mqtt-triggers-empty-add', + aiDescription: 'Add MQTT trigger' + }} + /> {:else if items?.length}
{#each items.slice(0, nbDisplayed) as { path, edited_by, edited_at, script_path, is_flow, extra_perms, canWrite, error, last_server_ping, server_id, mode, retry, error_handler_path, error_handler_args, labels, draft_only, is_draft } (path)} diff --git a/frontend/src/routes/(root)/(logged)/native_triggers/[service_name]/+page.svelte b/frontend/src/routes/(root)/(logged)/native_triggers/[service_name]/+page.svelte index fce11df6b9..f5e9a30dc5 100644 --- a/frontend/src/routes/(root)/(logged)/native_triggers/[service_name]/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/native_triggers/[service_name]/+page.svelte @@ -18,8 +18,9 @@ import { userStore, workspaceStore, userWorkspaces, usedTriggerKinds } from '$lib/stores' import CenteredPage from '$lib/components/CenteredPage.svelte' import PageHeader from '$lib/components/PageHeader.svelte' - import { Button, Alert, Skeleton } from '$lib/components/common' - import { LoaderCircle, Plus } from 'lucide-svelte' + import { Button, Alert, EmptyState, Skeleton } from '$lib/components/common' + import { LoaderCircle, Plus, Webhook } from 'lucide-svelte' + import { GithubIcon, GoogleIcon, NextcloudIcon } from '$lib/components/icons' import SearchItems from '$lib/components/SearchItems.svelte' import NoItemFound from '$lib/components/home/NoItemFound.svelte' import { page } from '$app/state' @@ -30,6 +31,11 @@ const serviceName = $derived(page.params.service_name as NativeServiceName) const serviceConfig = $derived(getServiceConfig(serviceName)) + const serviceIcons: Partial> = { + nextcloud: NextcloudIcon, + google: GoogleIcon, + github: GithubIcon + } let triggers: TriggerW[] = $state([]) let loading = $state(true) @@ -264,9 +270,19 @@ {/each} {:else if !triggers?.length} -
- No {serviceConfig?.serviceDisplayName || serviceName} triggers -
+ editor?.openNew(), + aiId: 'native-triggers-empty-add', + aiDescription: 'Add native trigger' + }} + /> {:else if items?.length} {/each} {:else if !triggers?.length} -
No NATS triggers
+ natsTriggerEditor?.openNew(false), + aiId: 'nats-triggers-empty-add', + aiDescription: 'Add NATS trigger' + }} + /> {:else if items?.length}
{#each items.slice(0, nbDisplayed) as { path, edited_by, edited_at, script_path, is_flow, nats_resource_path, subjects, extra_perms, canWrite, marked, server_id, error, last_server_ping, mode, retry, error_handler_path, error_handler_args, labels, draft_only, is_draft } (path)} diff --git a/frontend/src/routes/(root)/(logged)/postgres_triggers/+page.svelte b/frontend/src/routes/(root)/(logged)/postgres_triggers/+page.svelte index 193a3cb2ac..d1c07a7e75 100644 --- a/frontend/src/routes/(root)/(logged)/postgres_triggers/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/postgres_triggers/+page.svelte @@ -22,7 +22,7 @@ import { base } from '$app/paths' import { page } from '$app/stores' import CenteredPage from '$lib/components/CenteredPage.svelte' - import { Alert, Badge, Button, Skeleton } from '$lib/components/common' + import { Alert, Badge, Button, EmptyState, Skeleton } from '$lib/components/common' import Dropdown from '$lib/components/DropdownV2.svelte' import PageHeader from '$lib/components/PageHeader.svelte' import SharedBadge from '$lib/components/SharedBadge.svelte' @@ -414,7 +414,18 @@ {/each} {:else if !triggers?.length} -
No postgres triggers
+ postgresTriggerEditor?.openNew(false), + aiId: 'postgres-triggers-empty-add', + aiDescription: 'Add Postgres trigger' + }} + /> {:else if items?.length}
{#each items.slice(0, nbDisplayed) as { postgres_resource_path, publication_name, replication_slot_name, path, edited_by, error, edited_at, script_path, is_flow, extra_perms, canWrite, mode, server_id, retry, error_handler_path, error_handler_args, labels, draft_only, is_draft } (path)} diff --git a/frontend/src/routes/(root)/(logged)/resources/+page.svelte b/frontend/src/routes/(root)/(logged)/resources/+page.svelte index 27fdd286b5..b4cf764cb7 100644 --- a/frontend/src/routes/(root)/(logged)/resources/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/resources/+page.svelte @@ -3,7 +3,7 @@ import { page } from '$app/state' import AppConnect from '$lib/components/AppConnectDrawer.svelte' import CenteredPage from '$lib/components/CenteredPage.svelte' - import { Alert, Badge, Button, Skeleton, Tab } from '$lib/components/common' + import { Alert, Badge, Button, EmptyState, Skeleton, Tab, TabFade } from '$lib/components/common' import ConfirmationModal from '$lib/components/common/confirmationModal/ConfirmationModal.svelte' import Drawer from '$lib/components/common/drawer/Drawer.svelte' import DrawerContent from '$lib/components/common/drawer/DrawerContent.svelte' @@ -17,6 +17,7 @@ import { resourceTypesStore } from '$lib/components/resourceTypesStore' import SchemaViewer from '$lib/components/SchemaViewer.svelte' import FilterSearchbar, { + hasActiveFilters, useUrlSyncedFilterInstance, type FilterInstanceRec } from '$lib/components/FilterSearchbar.svelte' @@ -55,14 +56,18 @@ Braces, Building, Circle, + Database, FileUp, Link, + Palette, Pen, Plus, RotateCw, Save, + SearchX, Shield, - Trash + Trash, + Zap } from 'lucide-svelte' import { onMount, untrack } from 'svelte' import autosize from '$lib/autosize' @@ -627,6 +632,36 @@ let showTable = $derived( tab == 'workspace' || tab == 'states' || tab == 'cache' || tab == 'theme' ) + + let activeFilters = $derived(hasActiveFilters(filters.val)) + + const emptyStates: Record = { + workspace: { + icon: Boxes, + title: 'No resources yet', + description: + 'Resources hold the connection settings and credentials your scripts, flows and apps use to reach external systems.' + }, + states: { + icon: Database, + title: 'No states yet', + description: + 'States appear here once a script stores data to keep it persistent between runs of the same trigger.' + }, + cache: { + icon: Zap, + title: 'No cached results yet', + description: + 'Cached results appear here once a flow step with caching enabled has run at least once.' + }, + theme: { + icon: Palette, + title: 'No themes yet', + description: + 'Themes are CSS for the legacy low-code app editor only. Add one from the CSS panel of an app — they cannot be created here.' + } + } + let emptyState = $derived(emptyStates[tab] ?? emptyStates.workspace) {#snippet extra()} - Theme are actually resources (but excluded from the Workspace tab for clarity). Theme - are used by the apps to customize their look and feel. + Themes are actually resources (but excluded from the Workspace tab for clarity). They + are CSS for the legacy low-code app editor only, and are added from the CSS panel of + an app rather than from this page. {/snippet} @@ -989,384 +1025,403 @@ {/if}
- {#if showTable} -
- {#if loading.resources} - - {#each new Array(6) as _} - - {/each} - {:else if filteredItems?.length == 0} -
-
No resources found
-
- Try changing the filters or creating a new resource -
-
- {:else} - - - - - Path - Resource type - Description - - - - - - {#if filteredItems} - {#each filteredItems as { path, description, resource_type, extra_perms, canWrite, is_oauth, is_linked, account, refresh_error, is_expired, marked, is_refreshed, labels, inherited_labels, ws_specific, draft_only, is_draft }} - {@const hasDraft = - getLocalDraftHint($workspaceStore, 'resource', path) ?? is_draft} - - - - - -
+ + {#if showTable} +
+ {#if loading.resources} + + {#each new Array(6) as _} + + {/each} + {:else if filteredItems?.length == 0} + {#if activeFilters} + + {:else} + appConnect?.open?.(), + aiId: 'resources-empty-add-resource', + aiDescription: 'Add resource' + } + : undefined} + /> + {/if} + {:else} + + + + + Path + Resource type + Description + + + + + + {#if filteredItems} + {#each filteredItems as { path, description, resource_type, extra_perms, canWrite, is_oauth, is_linked, account, refresh_error, is_expired, marked, is_refreshed, labels, inherited_labels, ws_specific, draft_only, is_draft }} + {@const hasDraft = + getLocalDraftHint($workspaceStore, 'resource', path) ?? is_draft} + + + + + +
+ { + handledHash = `#/resource/${path}` + resourceEditor?.initEdit?.(path) + }} + >{#if marked}{@html marked}{:else}{path}{/if}{hasDraft ? '*' : ''} + + {#if labels?.length} +
+ {#each labels as label} + { + const arr = (filters.val.label ?? '').split(',').filter(Boolean) + const idx = arr.indexOf(label) + if (idx >= 0) arr.splice(idx, 1) + else arr.push(label) + const newFilters = { ...filters.val } + if (arr.length) newFilters.label = arr.join(',') + else delete newFilters.label + filters.val = newFilters + }}>{label} + {/each} +
+ {/if} + +
+ + { - handledHash = `#/resource/${path}` - resourceEditor?.initEdit?.(path) - }} - >{#if marked}{@html marked}{:else}{path}{/if}{hasDraft ? '*' : ''} - - {#if labels?.length} -
- {#each labels as label} - { - const arr = (filters.val.label ?? '').split(',').filter(Boolean) - const idx = arr.indexOf(label) - if (idx >= 0) arr.splice(idx, 1) - else arr.push(label) - const newFilters = { ...filters.val } - if (arr.length) newFilters.label = arr.join(',') - else delete newFilters.label - filters.val = newFilters - }}>{label} - {/each} -
- {/if} - -
- - - { - const linkedRt = resourceTypes?.find((rt) => rt.name === resource_type) - if (linkedRt) { - resourceTypeViewerObj = { - rt: linkedRt.name, - //@ts-ignore - schema: linkedRt.schema, - description: linkedRt.description ?? '', - formatExtension: linkedRt.format_extension, - isFileset: linkedRt.is_fileset ?? false + const linkedRt = resourceTypes?.find((rt) => rt.name === resource_type) + if (linkedRt) { + resourceTypeViewerObj = { + rt: linkedRt.name, + //@ts-ignore + schema: linkedRt.schema, + description: linkedRt.description ?? '', + formatExtension: linkedRt.format_extension, + isFileset: linkedRt.is_fileset ?? false + } + resourceTypeViewer?.openDrawer?.() + } else { + sendUserToast( + `Resource type ${resource_type} not found in workspace.`, + true + ) } - resourceTypeViewer?.openDrawer?.() - } else { - sendUserToast( - `Resource type ${resource_type} not found in workspace.`, - true - ) - } - }} - > - - - - - - {removeMarkdown(truncate(description ?? '', 30))} - - - -
-
- {#if is_linked} - - - {#snippet text()} -
- This resource is linked with a variable of the same path. They are - deleted and renamed together. -
- {/snippet} -
- {/if} -
-
- {#if is_refreshed} - - - {#snippet text()} -
- The OAuth token will be kept up-to-date in the background by - Windmill using its refresh token -
- {/snippet} -
- {/if} -
- - {#if is_oauth} -
- {#if refresh_error} + }} + > + + + + + + {removeMarkdown(truncate(description ?? '', 30))} + + + +
+
+ {#if is_linked} - + {#snippet text()}
- Latest exchange of the refresh token did not succeed. Error: {refresh_error} -
- {/snippet} -
- {:else if is_expired} - - - - {#snippet text()} -
- The access_token is expired, it will get renewed the next time - this variable is fetched or you can request is to be refreshed - in the dropdown on the right. -
- {/snippet} -
- {:else} - - - {#snippet text()} -
- The resource was connected through OAuth and the token is not - expired. + This resource is linked with a variable of the same path. They + are deleted and renamed together.
{/snippet}
{/if}
- {/if} -
-
- -
- {#if path && assetCanBeExplored({ kind: 'resource', path }, { resource_type }) && !$userStore?.operator} - - {/if} - { - shareModal?.openDrawer?.(path, 'resource') - } - }, - { - displayName: 'Edit', - icon: Pen, - disabled: !canWrite || !showCreateButtons, - action: () => { - resourceEditor?.initEdit?.(path) - } - }, - ...(!ws_specific && isDeployable('resource', path, deployUiSettings) - ? [ - { - displayName: 'Deploy to prod/staging', - icon: FileUp, - action: () => { - deploymentDrawer?.openDrawer(path, 'resource') +
+ {#if is_refreshed} + + + {#snippet text()} +
+ The OAuth token will be kept up-to-date in the background by + Windmill using its refresh token +
+ {/snippet} +
+ {/if} +
+ + {#if is_oauth} +
+ {#if refresh_error} + + + {#snippet text()} +
+ Latest exchange of the refresh token did not succeed. Error: {refresh_error} +
+ {/snippet} +
+ {:else if is_expired} + + + + {#snippet text()} +
+ The access_token is expired, it will get renewed the next time + this variable is fetched or you can request is to be refreshed + in the dropdown on the right. +
+ {/snippet} +
+ {:else} + + + {#snippet text()} +
+ The resource was connected through OAuth and the token is not + expired. +
+ {/snippet} +
+ {/if} +
+ {/if} +
+
+ +
+ {#if path && assetCanBeExplored({ kind: 'resource', path }, { resource_type }) && !$userStore?.operator} + + {/if} + { + shareModal?.openDrawer?.(path, 'resource') + } + }, + { + displayName: 'Edit', + icon: Pen, + disabled: !canWrite || !showCreateButtons, + action: () => { + resourceEditor?.initEdit?.(path) + } + }, + ...(!ws_specific && isDeployable('resource', path, deployUiSettings) + ? [ + { + displayName: 'Deploy to prod/staging', + icon: FileUp, + action: () => { + deploymentDrawer?.openDrawer(path, 'resource') + } } - } - ] - : []), - { - displayName: 'Delete', - disabled: !canWrite || !showCreateButtons, - icon: Trash, - type: 'delete', - action: (event) => { - // TODO - // @ts-ignore - if (event?.shiftKey) { - deleteResource(path, account) - } else { - deleteIsLinked = is_linked ?? false - deletePath = path - deleteConfirmedCallback = () => { + ] + : []), + { + displayName: 'Delete', + disabled: !canWrite || !showCreateButtons, + icon: Trash, + type: 'delete', + action: (event) => { + // TODO + // @ts-ignore + if (event?.shiftKey) { deleteResource(path, account) + } else { + deleteIsLinked = is_linked ?? false + deletePath = path + deleteConfirmedCallback = () => { + deleteResource(path, account) + } } } - } - }, - ...(account != undefined - ? [ - { - displayName: 'Refresh token', - icon: RotateCw, - action: async () => { - await OauthService.refreshToken({ - workspace: $workspaceStore ?? '', - id: account ?? 0, - requestBody: { - path - } - }) - sendUserToast('Token refreshed') - loadResources() + }, + ...(account != undefined + ? [ + { + displayName: 'Refresh token', + icon: RotateCw, + action: async () => { + await OauthService.refreshToken({ + workspace: $workspaceStore ?? '', + id: account ?? 0, + requestBody: { + path + } + }) + sendUserToast('Token refreshed') + loadResources() + } } - } - ] - : []) - ]} - /> -
- - {/each} - {/if} - - - {/if} -
- {:else if tab == 'types'} - {#if loading.types} - - {#each new Array(6) as _} - - {/each} - {:else if filteredResourceTypes?.length == 0} -
-
No resource types found
-
- Try changing the filters or creating a new resource type -
-
- {:else} -
- - - - Name - Description - - - - - {#if filteredResourceTypes} - {#each filteredResourceTypes as { name, description, schema, canWrite, format_extension, is_fileset }} - - - { - resourceTypeViewerObj = { - rt: name, - //@ts-ignore - schema: schema, - description: description ?? '', - formatExtension: format_extension, - isFileset: is_fileset ?? false - } - - resourceTypeViewer?.openDrawer?.() - }} + ] + : []) + ]} + /> +
- - - - - - {removeMarkdown(truncate(description ?? '', 200))} - - - - {#if !canWrite} - - Shared globally - - This resource type is from the 'admins' workspace shared with all - workspaces - - - {:else if $userStore?.is_admin || $userStore?.is_super_admin} -
- - -
- {:else} - - Non Editable - - Since resource types are shared with the whole workspace, only admins - can edit/delete them - - - {/if} -
- - {/each} - {/if} - - + + {/each} + {/if} + + + {/if}
+ {:else if tab == 'types'} + {#if loading.types} + + {#each new Array(6) as _} + + {/each} + {:else if filteredResourceTypes?.length == 0} +
+ +
+ {:else} +
+ + + + Name + Description + + + + + {#if filteredResourceTypes} + {#each filteredResourceTypes as { name, description, schema, canWrite, format_extension, is_fileset }} + + + { + resourceTypeViewerObj = { + rt: name, + //@ts-ignore + schema: schema, + description: description ?? '', + formatExtension: format_extension, + isFileset: is_fileset ?? false + } + + resourceTypeViewer?.openDrawer?.() + }} + > + + + + + + {removeMarkdown(truncate(description ?? '', 200))} + + + + {#if !canWrite} + + Shared globally + + This resource type is from the 'admins' workspace shared with all + workspaces + + + {:else if $userStore?.is_admin || $userStore?.is_super_admin} +
+ + +
+ {:else} + + Non Editable + + Since resource types are shared with the whole workspace, only admins + can edit/delete them + + + {/if} +
+
+ {/each} + {/if} + +
+
+ {/if} {/if} - {/if} + {/if} diff --git a/frontend/src/routes/(root)/(logged)/routes/+page.svelte b/frontend/src/routes/(root)/(logged)/routes/+page.svelte index 5f2a657480..53de5457d1 100644 --- a/frontend/src/routes/(root)/(logged)/routes/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/routes/+page.svelte @@ -22,7 +22,7 @@ import { base } from '$app/paths' import { page } from '$app/stores' import CenteredPage from '$lib/components/CenteredPage.svelte' - import { Button, Skeleton } from '$lib/components/common' + import { Button, EmptyState, Skeleton } from '$lib/components/common' import Dropdown from '$lib/components/DropdownV2.svelte' import PageHeader from '$lib/components/PageHeader.svelte' import SharedBadge from '$lib/components/SharedBadge.svelte' @@ -361,7 +361,18 @@ {/each} {:else if !triggers?.length} -
No routes
+ routeEditor?.openNew(false), + aiId: 'routes-empty-add', + aiDescription: 'Add route' + }} + /> {:else if items?.length}
{#each items.slice(0, nbDisplayed) as { summary, workspace_id, workspaced_route, mode, path, edited_by, edited_at, script_path, route_path, is_flow, extra_perms, canWrite, marked, http_method, static_asset_config, retry, error_handler_path, error_handler_args, draft_only, is_draft } (path)} diff --git a/frontend/src/routes/(root)/(logged)/schedules/+page.svelte b/frontend/src/routes/(root)/(logged)/schedules/+page.svelte index 7cbfffba7c..e403c981de 100644 --- a/frontend/src/routes/(root)/(logged)/schedules/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/schedules/+page.svelte @@ -10,7 +10,7 @@ import { withForkConflictRetry } from '$lib/utils/forkConflict' import { base } from '$app/paths' import CenteredPage from '$lib/components/CenteredPage.svelte' - import { Badge, Button, Skeleton } from '$lib/components/common' + import { Badge, Button, EmptyState, Skeleton } from '$lib/components/common' import Dropdown from '$lib/components/DropdownV2.svelte' import PageHeader from '$lib/components/PageHeader.svelte' import Popover from '$lib/components/Popover.svelte' @@ -21,6 +21,7 @@ import Toggle from '$lib/components/Toggle.svelte' import { userStore, workspaceStore, userWorkspaces, enterpriseLicense } from '$lib/stores' import { + Calendar, Circle, Copy, Eye, @@ -30,12 +31,14 @@ Pen, Play, Plus, + SearchX, Shield, Trash } from 'lucide-svelte' import { goto } from '$lib/navigation' import { sendUserToast } from '$lib/toast' import FilterSearchbar, { + hasActiveFilters, useUrlSyncedFilterInstance } from '$lib/components/FilterSearchbar.svelte' import { buildSchedulesFilterSchema } from '$lib/components/schedules/schedulesFilter' @@ -245,6 +248,8 @@ }) ) let filters = useUrlSyncedFilterInstance(untrack(() => schedulesFilterSchema)) + + let activeFilters = $derived(hasActiveFilters(filters.val)) let allFolders = $derived( Array.from( new Set( @@ -373,7 +378,26 @@ {/each} {:else if !schedules?.length} -
No schedules
+ {#if activeFilters} + + {:else} + scheduleEditor?.openNew(false), + aiId: 'schedules-empty-add', + aiDescription: 'Add schedule' + }} + /> + {/if} {:else if items?.length}
{#each items.slice(0, nbDisplayed) as { path, error, summary, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, extra_perms, canWrite, jobs, paused_until, labels, inherited_labels, draft_only, is_draft } (path)} diff --git a/frontend/src/routes/(root)/(logged)/sqs_triggers/+page.svelte b/frontend/src/routes/(root)/(logged)/sqs_triggers/+page.svelte index 41aa60d31b..1226fc2c0f 100644 --- a/frontend/src/routes/(root)/(logged)/sqs_triggers/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/sqs_triggers/+page.svelte @@ -21,7 +21,7 @@ import { base } from '$app/paths' import { page } from '$app/stores' import CenteredPage from '$lib/components/CenteredPage.svelte' - import { Alert, Badge, Button, Skeleton } from '$lib/components/common' + import { Alert, Badge, Button, EmptyState, Skeleton } from '$lib/components/common' import Dropdown from '$lib/components/DropdownV2.svelte' import PageHeader from '$lib/components/PageHeader.svelte' import SharedBadge from '$lib/components/SharedBadge.svelte' @@ -332,7 +332,18 @@ {/each} {:else if !triggers?.length} -
No sqs triggers
+ sqsTriggerEditor?.openNew(false), + aiId: 'sqs-triggers-empty-add', + aiDescription: 'Add SQS trigger' + }} + /> {:else if items?.length}
{#each items.slice(0, nbDisplayed) as { path, edited_by, error, edited_at, script_path, is_flow, extra_perms, canWrite, mode, server_id, retry, error_handler_path, error_handler_args, labels, draft_only, is_draft } (path)} diff --git a/frontend/src/routes/(root)/(logged)/variables/+page.svelte b/frontend/src/routes/(root)/(logged)/variables/+page.svelte index 787353bdf8..0a23656514 100644 --- a/frontend/src/routes/(root)/(logged)/variables/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/variables/+page.svelte @@ -1,7 +1,16 @@ -{#if assets.value && assets.value.length > 0} +{#if assets.status === 'idle' || assets.status === 'loading'} + +{:else if assets.value && assets.value.assets.length > 0}
    - {#each assets.value ?? [] as asset} -
  • + {#each assets.value.assets as asset} +
  • {asset.path} @@ -90,17 +139,26 @@ })}
    - + {#if asset.access_type} + {formatAssetAccessType(asset.access_type)} + {/if} +
  • {/each}
+ {#if assets.value.truncated} +
+ This run touched more assets than are listed here. +
+ {/if} {:else} -
No assets found
+
+ No assets found + + Assets detected while a run executes are recorded asynchronously, and only the most recent + runs that touched an asset keep that record. + +
{/if} From 6783a396b144948fa60324eae888bc4a83917bc8 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Tue, 18 Aug 2026 10:32:26 +0200 Subject: [PATCH 45/60] fix(api): document cache_ignore_s3_path on the Script read schema (#10742) Co-authored-by: Claude Opus 5 (1M context) --- backend/windmill-api/openapi.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index f8579f9aa4..392229e05b 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -26421,6 +26421,8 @@ components: type: integer cache_ttl: type: number + cache_ignore_s3_path: + type: boolean dedicated_worker: type: boolean ws_error_handler_muted: From 8492b4b4ba53b9061c479609e4c2cfe8d0e32427 Mon Sep 17 00:00:00 2001 From: hugocasa Date: Tue, 18 Aug 2026 12:24:53 +0200 Subject: [PATCH 46/60] chore: prove scratch file ops per command segment (#10744) * fix(agents): prove scratch file ops per command segment Co-Authored-By: Claude Opus 5 (1M context) * docs: describe the checkout root in the scratch guidance Co-Authored-By: Claude Opus 5 (1M context) * fix(agents): close two auto-allow holes in the scratch guards Co-Authored-By: Claude Opus 5 (1M context) * fix(agents): keep redirects and chained writes off the allow path Co-Authored-By: Claude Opus 5 (1M context) * fix(agents): never prove a command carrying a substitution or relative cd Co-Authored-By: Claude Opus 5 (1M context) * fix(agents): treat sibling checkouts as separate roots Co-Authored-By: Claude Opus 5 (1M context) * fix(agents): prove where a directory-form cp or mv actually lands Co-Authored-By: Claude Opus 5 (1M context) * fix(agents): leave directory-form cp and mv unproved Co-Authored-By: Claude Opus 5 (1M context) * docs: state the one-write-per-line rule in the scratch guidance Co-Authored-By: Claude Opus 5 (1M context) * docs: prefer Edit/Write over shell edits in agent guidance Co-Authored-By: Claude Opus 5 (1M context) * fix(agents): stop a failed cd from hiding the directory form Co-Authored-By: Claude Opus 5 (1M context) * refactor(agents): state the glob and cd rationale once Co-Authored-By: Claude Opus 5 (1M context) --------- Co-authored-by: Claude Opus 5 (1M context) --- .claude/hooks/allow-fileops-in-tmp.sh | 308 ++++++++++++++++++-------- .claude/hooks/guard-rm-outside-tmp.sh | 189 +++++++++------- .claude/hooks/lib-guarded-verb.sh | 191 ++++++++++++---- .claude/hooks/test-hooks.sh | 80 ++++++- AGENTS.md | 17 +- 5 files changed, 567 insertions(+), 218 deletions(-) diff --git a/.claude/hooks/allow-fileops-in-tmp.sh b/.claude/hooks/allow-fileops-in-tmp.sh index 665c23cf0a..87ce6541aa 100755 --- a/.claude/hooks/allow-fileops-in-tmp.sh +++ b/.claude/hooks/allow-fileops-in-tmp.sh @@ -1,17 +1,31 @@ #!/usr/bin/env bash -# PreToolUse allowance for scratch file ops: auto-allow a single, plain, single-line -# `mkdir` / `cp` / `mv` / `touch` / `chmod` / `tar` / `unzip` whose every path operand -# resolves under /tmp. Anything else makes no decision (exit 0) and falls back to the normal -# permission flow, except for `mv` and `chmod`: those get an explicit `ask`, the only prompt -# they get (see lib-guarded-verb.sh). +# PreToolUse allowance for scratch file ops: auto-allow `mkdir` / `cp` / `mv` / `touch` / +# `chmod` whose every path operand resolves inside one of the roots `path_class` recognizes — +# under /tmp, or inside a git working tree under $HOME — and `tar` / `unzip` confined to /tmp. +# Anything else makes no decision (exit 0) and falls back to the normal permission flow, except +# for `mv` and `chmod`: those get an explicit `ask`, the only prompt they get (see +# lib-guarded-verb.sh). +# +# The command is read one segment at a time, so chaining and line breaks carry no weight of +# their own: `cd /tmp/scratch && mv /tmp/a /tmp/b` is proved on the operands of the `mv`. A +# decision covers the whole command line, so `allow` is emitted only when every segment is one +# of these verbs proved here or a `cd` that resolved, AND exactly one of them writes (see the +# gate at the foot of this file — an earlier write can change what a later operand means). A +# line that mixes a proven op with some other command makes no decision instead and leaves that +# line to the normal permission flow, rather than waving an unexamined command through with it. # # This is a hook rather than an allow rule because permission rules match a command prefix, so # they can only constrain the FIRST operand. `cp /tmp/x ~/.zshrc` matches a `cp /tmp/` prefix, # and requiring every operand is the point. # -# Requiring the sources under /tmp too (not just the destination) keeps this from becoming a -# read-exfiltration path around the `Read(**/.env)` / `Read(**/secrets/**)` deny rules: a copy -# out of the project into /tmp would land the content somewhere `Read(/tmp/**)` allows. +# One operation may not straddle two roots, sources included, and a sibling checkout is a +# different root — `path_class` names the git tree, not just its kind. A copy out of a checkout +# into /tmp would be a read-exfiltration path around the `Read(**/secrets/**)` / `Read(**/*.pem)` +# deny rules, since the content lands where `Read(/tmp/**)` allows it to be read back, and one +# out of a repo the Read tool is not confined to would do the same for that repo. Keeping every +# operand of one operation inside a single root closes both without restating those rules here. +# The checkout root itself is what makes an in-repo `mv` or `chmod` auto-allowable: deleting a +# file there has never prompted, and moving or chmod-ing one is not the graver act. # # Deny-by-default tokenizing, in the same spirit as guard-rm-outside-tmp.sh: every path token # must consist only of alphanumerics and `. _ / -`. That set contains none of the characters @@ -19,12 +33,19 @@ # any glob character, so all of those forms fail by construction. `realpath -m` then resolves # `..` and existing symlinks, so `/tmp/link` pointing at /etc/passwd is caught. # +# `tar` and `unzip` keep the stricter rule — /tmp only, and absolute operands only — because +# their positional grammar makes a bare word ambiguous: `tar P -xf ...` is --absolute-names, +# not a file named P, and resolving it as a path would put an option in a root and allow it. +# The other five take relative operands, resolved against the working directory that `cd` +# tracking maintains, since for those a bare word really is a path (a GNU option starts with +# `-`, and the option allowlist below rejects the ones that would change symlink handling). +# # `tar` and `unzip` get their own parser: their write destination arrives as a flag VALUE # (`-C`, `-d`) rather than a positional, and a bundle like `-xzf` consumes the token after it. # Flags are an allowlist, not a denylist, so `-P` / `--absolute-names` — which turn off tar's # refusal to extract `..` and absolute member paths — defer rather than needing enumeration. -# Extraction additionally requires an explicit destination under /tmp, or a cwd already under -# /tmp, since otherwise members land in the project checkout. +# Extraction additionally requires an explicit destination under /tmp, or a working directory +# already under /tmp, since otherwise members land in the project checkout. # # Residual risk accepted: an archive whose members include a symlink pointing out of /tmp # followed by a write through it can still escape, because tar applies member symlinks as it @@ -52,25 +73,51 @@ defer() { exit 0 } -# A newline separates commands, and the tokenizer below only reads the first line — defer. -case "$cmd" in *$'\n'*) defer "multi-line command" ;; esac +has_substitution "$cmd" && defer "command substitution in the command line" -read -r -a toks <<< "$cmd" +# 0 iff the token is a literal path this hook may reason about. A glob never auto-allows: bash +# expands it only after the hook has decided, so realpath sees the unexpanded pattern — +# `/tmp/link*` canonicalizes to itself and passes, then expands onto a symlink whose target is +# outside, and `cp` and `chmod` follow a command-line symlink, so that is a write to the target. +# (guard-rm-outside-tmp.sh can allow globs because `rm` unlinks the symlink rather than following +# it.) The charset holds none of the characters bash uses for quoting, expansion or separation. +literal_path() { + case "$1" in *[*?[]*) return 1 ;; esac + [ -z "$(printf '%s' "$1" | tr -d 'A-Za-z0-9._/-')" ] +} -# 0 iff the token is charset-safe and resolves to a path strictly inside /tmp. +# Prints the root class of a path token, then the path it resolved to on a second line, +# resolving a relative one against the tracked working directory. Fails, printing nothing, +# when the token is unsafe to reason about or lands outside every root. +operand_class() { + local t="$1" canon alt cls alt_cls="" + literal_path "$t" || return 1 + case "$t" in + /*) canon=$(realpath -m -- "$t" 2>/dev/null) ;; + *) # A `cd` may fail at runtime and leave the command where it started, so a relative + # operand has to land in the same root either way. + [ -n "$seg_cwd" ] || return 1 + canon=$(realpath -m -- "$seg_cwd/$t" 2>/dev/null) + if [ -n "$alt_cwd" ]; then + alt=$(realpath -m -- "$alt_cwd/$t" 2>/dev/null) + [ -n "$alt" ] || return 1 + alt_cls=$(path_class "$alt") || return 1 + fi + ;; + esac + [ -n "$canon" ] || return 1 + cls=$(path_class "$canon") || return 1 + [ -n "$alt_cls" ] && [ "$alt_cls" != "$cls" ] && return 1 + # Class and resolved path together: a caller runs this in a command substitution, so a global + # set here would be set in that subshell and lost. + printf '%s\n%s' "$cls" "$canon" +} + +# 0 iff the token is charset-safe and resolves to a path strictly inside /tmp. The archive +# parser's stricter check; everything else goes through operand_class. under_tmp() { local t="$1" canon - # Globs never auto-allow. Bash expands them only after this hook has decided, so realpath - # sees the unexpanded pattern: `/tmp/link*` canonicalizes to itself and passes, then - # expands onto a symlink whose target is outside /tmp. chmod and cp follow command-line - # symlinks, so that is a write to the target. guard-rm-outside-tmp.sh can allow globs - # because `rm` unlinks the symlink itself rather than following it. - case "$t" in *[*?[]*) return 1 ;; esac - [ -n "$(printf '%s' "$t" | tr -d 'A-Za-z0-9._/-')" ] && return 1 - # Absolute only. Resolving a relative operand against the cwd makes any bare word look like - # a safe path whenever the cwd is under /tmp, while the tool itself reads it as an option: - # `tar P -xf ...` is --absolute-names, not ./P, and `cp /tmp/t -RL /tmp/o` is a - # dereferencing recursive copy, not a file named -RL. + literal_path "$t" || return 1 case "$t" in /*) ;; *) return 1 ;; esac canon=$(realpath -m -- "$t" 2>/dev/null) [ -n "$canon" ] || return 1 @@ -79,29 +126,16 @@ under_tmp() { return 1 } -# Bare command word only; wrappers (`timeout cp`), env prefixes, and `/bin/cp` defer. -# Options are an allowlist per command, so anything that changes how symlinks are followed -# defers instead of needing enumeration. `cp -L` / `-H` matter most: they dereference while -# recursing, which copies the CONTENT of a symlink target from outside /tmp into a scratch -# dir that `Read(/tmp/**)` then exposes. Plain `-r` and `-a` (which implies `-d`) recreate -# such a symlink as a symlink instead, so no outside content is materialized. -case "${toks[0]:-}" in - mkdir) takes_mode=0; ok_opts='pv' ;; - cp) takes_mode=0; ok_opts='rRvfnpa' ;; - mv) takes_mode=0; ok_opts='vfn' ;; - touch) takes_mode=0; ok_opts='acmv' ;; - chmod) takes_mode=1; ok_opts='Rvfc' ;; # chmod's first operand is a mode, not a path - tar) ok_flags='xctzjJavfC'; val_flags='fC' ;; - unzip) ok_flags='oqnljvd'; val_flags='d' ;; - *) defer "not the leading command word" ;; -esac - -# ---------------------------------------------------------------- tar / unzip -if [ -n "${ok_flags:-}" ]; then - saw_archive=0 saw_dest=0 extracting=0 listing=0 end_opts=0 - i=1 - while [ "$i" -lt "${#toks[@]}" ]; do - t="${toks[$i]}" +# Proves one `tar` / `unzip` segment ($1 = the verb), whose tokens are in SEG_TOKS. +check_archive_segment() { + local verb="$1" ok_flags val_flags t flags val + local saw_archive=0 saw_dest=0 extracting=0 listing=0 end_opts=0 i=1 + case "$verb" in + tar) ok_flags='xctzjJavfC'; val_flags='fC' ;; + unzip) ok_flags='oqnljvd'; val_flags='d' ;; + esac + while [ "$i" -lt "${#SEG_TOKS[@]}" ]; do + t="${SEG_TOKS[$i]}" i=$((i + 1)) if [ "$end_opts" = 0 ]; then [ "$t" = "--" ] && { end_opts=1; continue; } @@ -112,13 +146,13 @@ if [ -n "${ok_flags:-}" ]; then # leave a residue here and defer rather than being enumerated as denials. [ -n "$(printf '%s' "$flags" | tr -d "$ok_flags")" ] && defer "unrecognized option \`$t\`" case "$flags" in *x*) extracting=1 ;; esac - case "${toks[0]}$flags" in unzip*[lv]*) listing=1 ;; esac + case "$verb$flags" in unzip*[lv]*) listing=1 ;; esac # A flag consuming the next token must be alone in its bundle's final position # (`-xzf a.tar`), else the token it eats is ambiguous. case "${flags%?}" in *[$val_flags]*) defer "ambiguous option bundle \`$t\`" ;; esac case "${flags: -1}" in [$val_flags]) - val="${toks[$i]:-}" + val="${SEG_TOKS[$i]:-}" i=$((i + 1)) [ -n "$val" ] || defer "option \`$t\` has no value" under_tmp "$val" || defer "\`$val\` is outside /tmp" @@ -136,54 +170,152 @@ if [ -n "${ok_flags:-}" ]; then # first is the archive. Requiring every one under /tmp is conservative for member names, # which are not filesystem paths — those defer rather than being wrongly allowed. under_tmp "$t" || defer "\`$t\` is outside /tmp" - [ "${toks[0]}" = "unzip" ] && saw_archive=1 + [ "$verb" = "unzip" ] && saw_archive=1 done # tar without -f reads a tape/stdin; unzip needs an archive [ "$saw_archive" = 1 ] || defer "no archive operand" # Writes land relative to the working directory unless a destination was given. `unzip -l` # and `-v` only list, so they need no destination. - if [ "$extracting" = 1 ] || { [ "${toks[0]}" = "unzip" ] && [ "$listing" = 0 ]; }; then - [ "$saw_dest" = 1 ] || under_tmp "${cwd:-$PWD}" || defer "extraction target is outside /tmp" + if [ "$extracting" = 1 ] || { [ "$verb" = "unzip" ] && [ "$listing" = 0 ]; }; then + # An extraction with no destination lands in the working directory. Word splitting cannot + # tell a `cd` inside a quoted string from one the shell runs, and believing a false one + # would put an archive's members in the checkout, so once any `cd` is in the line only an + # explicit destination will do. + [ "$saw_dest" = 1 ] \ + || { [ "$saw_cd" = 0 ] && [ -n "$seg_cwd" ] && under_tmp "$seg_cwd"; } \ + || defer "extraction target is outside /tmp" fi - decide allow "archive paths and extraction target are under /tmp" -fi +} -# ------------------------------------------- mkdir / cp / mv / touch / chmod -path_operand=0 -seen_mode=0 -end_opts=0 -i=1 -while [ "$i" -lt "${#toks[@]}" ]; do - t="${toks[$i]}" - i=$((i + 1)) +# Proves one `mkdir` / `cp` / `mv` / `touch` / `chmod` segment ($1 = the verb), whose tokens +# are in SEG_TOKS. +check_fileops_segment() { + local verb="$1" takes_mode ok_opts t cls resolved seen_class="" + local path_operand=0 seen_mode=0 end_opts=0 i=1 rel_operand=0 + local -a ops=() + # Options are an allowlist per command, so anything that changes how symlinks are followed + # defers instead of needing enumeration. `cp -L` / `-H` matter most: they dereference while + # recursing, which copies the CONTENT of a symlink target from outside /tmp into a scratch + # dir that `Read(/tmp/**)` then exposes. Plain `-r` and `-a` (which implies `-d`) recreate + # such a symlink as a symlink instead, so no outside content is materialized. + case "$verb" in + mkdir) takes_mode=0; ok_opts='pv' ;; + cp) takes_mode=0; ok_opts='rRvfnpa' ;; + mv) takes_mode=0; ok_opts='vfn' ;; + touch) takes_mode=0; ok_opts='acmv' ;; + chmod) takes_mode=1; ok_opts='Rvfc' ;; # chmod's first operand is a mode, not a path + esac + while [ "$i" -lt "${#SEG_TOKS[@]}" ]; do + t="${SEG_TOKS[$i]}" + i=$((i + 1)) - if [ "$end_opts" = 0 ]; then - [ "$t" = "--" ] && { end_opts=1; continue; } - # Checked at any position, not just before the first operand: GNU utils permute, so - # `cp /tmp/tree -RL /tmp/out` still enables dereferencing recursion. - case "$t" in - -?*) - # Allowlist: long options and the dereferencing flags leave a residue and defer. - [ -n "$(printf '%s' "${t#-}" | tr -d "$ok_opts")" ] && defer "unrecognized option \`$t\`" - continue - ;; - esac - fi + if [ "$end_opts" = 0 ]; then + [ "$t" = "--" ] && { end_opts=1; continue; } + # Checked at any position, not just before the first operand: GNU utils permute, so + # `cp /tmp/tree -RL /tmp/out` still enables dereferencing recursion. + case "$t" in + -?*) + # Allowlist: long options and the dereferencing flags leave a residue and defer. + [ -n "$(printf '%s' "${t#-}" | tr -d "$ok_opts")" ] && defer "unrecognized option \`$t\`" + continue + ;; + esac + fi - # chmod: consume the mode operand without a path check. Octal, or symbolic clauses. - if [ "$takes_mode" = 1 ] && [ "$seen_mode" = 0 ]; then - case "$t" in - [0-7] | [0-7][0-7] | [0-7][0-7][0-7] | [0-7][0-7][0-7][0-7]) ;; - *) printf '%s' "$t" | grep -Eq '^[ugoa]*[+=-][rwxXst]*(,[ugoa]*[+=-][rwxXst]*)*$' || defer "unrecognized mode \`$t\`" ;; - esac - seen_mode=1 - continue - fi + # chmod: consume the mode operand without a path check. Octal, or symbolic clauses. + if [ "$takes_mode" = 1 ] && [ "$seen_mode" = 0 ]; then + case "$t" in + [0-7] | [0-7][0-7] | [0-7][0-7][0-7] | [0-7][0-7][0-7][0-7]) ;; + *) printf '%s' "$t" | grep -Eq '^[ugoa]*[+=-][rwxXst]*(,[ugoa]*[+=-][rwxXst]*)*$' || defer "unrecognized mode \`$t\`" ;; + esac + seen_mode=1 + continue + fi - under_tmp "$t" || defer "\`$t\` is outside /tmp" - path_operand=1 + resolved=$(operand_class "$t") || defer "\`$t\` is outside /tmp and not inside a git checkout in \$HOME" + cls="${resolved%%$'\n'*}" + # Every operand of one operation stays in one root: see the exfiltration note above. + [ -n "$seen_class" ] && [ "$cls" != "$seen_class" ] && defer "\`$t\` puts this $verb across two roots" + seen_class="$cls" + ops+=("${resolved#*$'\n'}") + case "$t" in /*) ;; *) rel_operand=1 ;; esac + path_operand=1 + done + + [ "$path_operand" = 1 ] || defer "no path operand" + + # In directory form the command writes a path it does not name: `cp x dir` writes `dir/x`, + # and `cp` follows that child when it is a symlink — this checkout is full of them, every + # `*_ee.rs` pointing into the sibling EE repo. Deriving that child would mean reproducing + # which name the tool picks (the operand as written, not as resolved — a symlinked source + # keeps its own name) and how deep `-r` recurses. The form is left unproved instead. + case "$verb" in + cp | mv) + [ "${#ops[@]}" -ge 2 ] || return 0 + # Whether the destination is an existing directory is itself a question about which of + # the two candidate working directories the command ran in, and only one of them is in + # `ops`. A `cd` that fails at runtime would otherwise let the form through: the + # destination resolved against the directory the command never reached is some path that + # does not exist, while the one it actually ran in is a directory full of symlinks. + [ -n "$alt_cwd" ] && [ "$rel_operand" = 1 ] \ + && defer "a relative operand after a \`cd\` lands in one of two directories" + [ -d "${ops[-1]}" ] \ + && defer "\`${ops[-1]}\` already exists as a directory, so this $verb writes a path it does not name" + ;; + esac +} + +split_segments "$cmd" +seg_cwd="${cwd:-$PWD}" +alt_cwd="" # where a `cd` that failed would have left the command +saw_cd=0 # a `cd` moved the working directory somewhere +proved=0 # how many ops came out inside a single root +only_ours=1 # ... and nothing else shares the command line + +for seg in "${SEGMENTS[@]}"; do + segment_tokens "$seg" + case "${SEG_TOKS[0]:-}" in + "") continue ;; + mkdir | cp | mv | touch | chmod) + check_fileops_segment "${SEG_TOKS[0]}" + proved=$((proved + 1)) + continue + ;; + tar | unzip) + check_archive_segment "${SEG_TOKS[0]}" + proved=$((proved + 1)) + continue + ;; + cd) + # A `cd` writes nothing, so it never blocks an allow; it only moves where a later relative + # operand points, to one of the two candidates `apply_cd` describes. + if [ "$saw_cd" = 0 ] && new_cwd=$(apply_cd "$seg_cwd" "${SEG_TOKS[@]:1}"); then + alt_cwd="$seg_cwd" + seg_cwd="$new_cwd" + else + # Not the harmless segment an allow assumes: whatever this guard could not account for + # may be a redirect, and a redirect writes. Leave the line to the normal flow. + seg_cwd="" alt_cwd="" + only_ours=0 + fi + saw_cd=1 + continue + ;; + esac + # Some other command shares the line. If an `mv` or `chmod` runs inside it after all — behind + # a wrapper, an env prefix or a path — this hook cannot say what it writes to. + for verb in mv chmod; do + segment_runs_verb "$verb" "$seg" && defer "$verb is not the leading command word in \`$seg\`" + done + only_ours=0 done -[ "$path_operand" = 1 ] || defer "no path operand" -decide allow "every path operand is under /tmp" +# Exactly one write per line. Each segment is proved against the filesystem as it stands now, +# and an earlier write can change what a later operand means: `cp -r /tmp/tree /tmp/live` that +# recreates a symlink out of /tmp turns `/tmp/live/link` — a path under /tmp when this ran — +# into a write through that symlink. Deletes compose safely and guard-rm-outside-tmp.sh allows +# several, because `rm` unlinks a symlink rather than following it. +[ "$proved" -ge 1 ] || exit 0 +[ "$only_ours" = 1 ] && [ "$proved" = 1 ] && decide allow "every path operand is inside a single root" +exit 0 diff --git a/.claude/hooks/guard-rm-outside-tmp.sh b/.claude/hooks/guard-rm-outside-tmp.sh index 5aff497d89..4d253ffc62 100755 --- a/.claude/hooks/guard-rm-outside-tmp.sh +++ b/.claude/hooks/guard-rm-outside-tmp.sh @@ -1,14 +1,17 @@ #!/usr/bin/env bash -# PreToolUse guard for `rm`: auto-allow ONLY a single, plain, single-line `rm` whose every -# operand is a whitelisted target — under /tmp, or inside a git working tree located in $HOME -# (a version-controlled project dir). Any other command that runs `rm` gets an explicit `ask`, -# which is the ordinary permission prompt and the only one `rm` gets (see lib-guarded-verb.sh); -# a command that runs no `rm` at all makes no decision (exit 0). +# PreToolUse guard for `rm`: auto-allow deletes whose every operand is a whitelisted target — +# under /tmp, or inside a git working tree located in $HOME (a version-controlled project dir). +# Any other command that runs `rm` gets an explicit `ask`, which is the ordinary permission +# prompt and the only one `rm` gets (see lib-guarded-verb.sh); a command that runs no `rm` at +# all makes no decision (exit 0). # -# The git-tree allowance trades on "this is a project under version control" being lower-stakes -# than a delete elsewhere — NOT on full recoverability: committed content is restorable via git, -# but untracked / .gitignore'd / uncommitted content, and an independent nested repo's history -# under a recursively-deleted parent, are NOT. Accepted as a deliberate convenience tradeoff. +# The command is read one segment at a time, so chaining and line breaks carry no weight of +# their own: `rm -f /tmp/a && rm -rf /tmp/b` is two deletes, each proved on its own operands. +# A decision covers the whole command line, so `allow` is emitted only when every segment is +# an `rm` this guard proved or a `cd` it could resolve. A line that mixes a proven `rm` with +# some other command makes no decision instead and leaves that line to the normal permission +# flow: the delete is not what needed a prompt, and waving the rest of the line through with +# it would turn a trailing `rm -f /tmp/x` into a way to auto-approve anything. # # Deny-by-default: every token must consist only of a safe character set (alphanumerics, # `. _ / -` and glob chars `* ? [ ]`). That set contains none of the characters bash uses for @@ -17,12 +20,12 @@ # and existing symlinks (so a symlink out of the allowed roots is caught), and a wildcard in a # non-final path segment is refused because it can expand through a symlink realpath can't see. # -# The git-repo allowance covers targets inside a git working tree under $HOME, and the tree's -# own root folder only when it is a linked worktree (`.git` is a pointer file, so history in -# the main repo survives); a primary checkout's root (`.git` is a history dir) and any `.git`, -# `.claude` or `.env` path are never auto-allowed. Globs auto-allow only under /tmp — elsewhere their expansion +# Which targets those two roots cover, and the tradeoff they rest on, is `path_class` in +# lib-guarded-verb.sh. Globs auto-allow only under /tmp — elsewhere their expansion # could reach `.git` or a dotfile the literal checks never see. Relative operands resolve -# against the command's cwd (from the hook input). +# against the working directory the command runs from, which a `cd` in an earlier segment +# moves; once a `cd` is one this guard cannot resolve, that directory is unknown and a +# relative operand can no longer be proved. # # Assumes GNU `realpath` (-m) and `jq`, both present in this repo's Linux dev env. set -uo pipefail @@ -35,87 +38,103 @@ cmd=$(printf '%s' "$input" | jq -r '.tool_input.command // empty' 2>/dev/null) cwd=$(printf '%s' "$input" | jq -r '.cwd // empty' 2>/dev/null) # Every bail-out below goes through `defer`, so the forms this guard refuses to reason about — -# compound, quoted, wrapped — still reach the user as a prompt whenever an `rm` runs among them. +# wrapped, quoted, expanded — still reach the user as a prompt whenever an `rm` runs among them. runs_verb rm "$cmd" && guarded=1 || guarded=0 defer() { [ "$guarded" = 1 ] && decide ask "$1" exit 0 } -# A newline separates commands, and the tokenizer below only reads the first line — defer. -case "$cmd" in *$'\n'*) defer "multi-line command" ;; esac +has_substitution "$cmd" && defer "command substitution in the command line" -read -r -a toks <<< "$cmd" -# Bare leading `rm` only; wrappers (`timeout rm`), env prefixes, and `/bin/rm` defer. -[ "${toks[0]:-}" = "rm" ] || defer "rm is not the leading command word" - -# 0 (allow) iff the canonical path is an auto-allowable rm target: under /tmp, or strictly -# inside a git working tree located under $HOME. The walk stops at $HOME, so a dotfiles repo at -# ~ can't make all of $HOME deletable, and top-level ~ files stay protected. -allowed_target() { - local canon="$1" d root="" - case "$canon" in /tmp/?*) return 0 ;; esac - [ -n "${HOME:-}" ] || return 1 - case "$canon" in "$HOME"/?*) ;; *) return 1 ;; esac - # Never auto-allow: history, and the two kinds of path the "it's under version control" - # premise doesn't hold for — the agent's own guards and settings (deleting them is what - # removes the prompt on everything else), and gitignored `.env` files. - case "$canon" in - *"/.git" | *"/.git/"* | *"/.claude" | *"/.claude/"*) return 1 ;; - *"/.env" | *"/.env."*) return 1 ;; - esac - d="$canon" - while [ "$d" != "/" ] && [ "$d" != "$HOME" ]; do - [ -e "$d/.git" ] && { root="$d"; break; } - d=$(dirname "$d") +# Proves one `rm` segment, whose tokens are in SEG_TOKS with `rm` at index 0, resolving relative +# operands against $seg_cwd. Returns only once every operand is an auto-allowable target; +# anything it cannot prove defers instead. +check_rm_segment() { + local i=1 t canon candidates had_operand=0 end_opts=0 + while [ "$i" -lt "${#SEG_TOKS[@]}" ]; do + t="${SEG_TOKS[$i]}" + i=$((i + 1)) + # Whitelist every token (flags included, so an operator hidden in a flag like `-rf;rm` + # can't slip past): any character outside the safe set makes it unsafe to reason about. + [ -n "$(printf '%s' "$t" | tr -d 'A-Za-z0-9._/*?[]-')" ] && defer "unsafe characters in \`$t\`" + # A glob in an option-looking token (`-[-]`) can expand to `--` and turn a later `-name` + # into an operand — never a real option, so defer. + case "$t" in -*[*?[]*) defer "glob inside the option \`$t\`" ;; esac + if [ "$end_opts" = 0 ]; then + [ "$t" = "--" ] && { end_opts=1; continue; } + # Skip real options only before the first operand. A bare `-` is a filename, and under + # POSIXLY_CORRECT GNU rm stops option parsing at the first operand, so a later `-name` + # is a filename too — validate it rather than skipping it. + if [ "$had_operand" = 0 ]; then + case "$t" in -?*) continue ;; esac + fi + fi + had_operand=1 + # No wildcard in a non-final path segment (`a/*/b`): it can expand through a symlink + # realpath can't see. A slashless glob (`*.rs`) is a final-segment match — fine. + case "$t" in */*) case "${t%/*}" in *[*?[]*) defer "glob in a non-final segment of \`$t\`" ;; esac ;; esac + # A relative operand has as many candidate paths as the command has candidate working + # directories, and every one of them has to be auto-allowable: a `cd` that fails at runtime + # leaves the delete running in the directory it started in. + case "$t" in + /*) candidates=$(realpath -m -- "$t" 2>/dev/null) ;; + *) [ -n "$seg_cwd" ] || defer "\`$t\` is relative to a working directory this guard cannot pin down" + candidates=$(realpath -m -- "$seg_cwd/$t" 2>/dev/null) + [ -n "$alt_cwd" ] && candidates="$candidates +$(realpath -m -- "$alt_cwd/$t" 2>/dev/null)" + ;; + esac + while IFS= read -r canon; do + [ -n "$canon" ] || defer "cannot resolve \`$t\`" + # A glob may auto-allow only under /tmp, where everything is deletable. Elsewhere its + # expansion could match `.git`, a dotfile like `.*`, or a nested checkout root that the + # literal-path checks never see — so require literal operands in git repos. + case "$t" in *[*?[]*) case "$canon" in /tmp/?*) ;; *) defer "glob \`$t\` is outside /tmp" ;; esac ;; esac + path_class "$canon" >/dev/null || defer "\`$canon\` is outside /tmp and not inside a git checkout in \$HOME" + done <<< "$candidates" done - [ -n "$root" ] || return 1 # not inside a git working tree under $HOME - if [ "$canon" = "$root" ]; then - # Deleting the repo root folder itself: allow only for a linked worktree, whose `.git` is - # a file/pointer so the history lives in the main repo and survives. A primary checkout's - # `.git` is a directory holding the history, so deleting it is unrecoverable — defer. - [ -f "$root/.git" ] && return 0 - return 1 - fi - return 0 + [ "$had_operand" = 1 ] || defer "no operand" } -had_operand=0 -end_opts=0 -i=1 -while [ "$i" -lt "${#toks[@]}" ]; do - t="${toks[$i]}" - i=$((i + 1)) - # Whitelist every token (flags included, so an operator hidden in a flag like `-rf;rm` - # can't slip past): any character outside the safe set makes it unsafe to reason about. - [ -n "$(printf '%s' "$t" | tr -d 'A-Za-z0-9._/*?[]-')" ] && defer "unsafe characters in \`$t\`" - # A glob in an option-looking token (`-[-]`) can expand to `--` and turn a later `-name` - # into an operand — never a real option, so defer. - case "$t" in -*[*?[]*) defer "glob inside the option \`$t\`" ;; esac - if [ "$end_opts" = 0 ]; then - [ "$t" = "--" ] && { end_opts=1; continue; } - # Skip real options only before the first operand. A bare `-` is a filename, and under - # POSIXLY_CORRECT GNU rm stops option parsing at the first operand, so a later `-name` - # is a filename too — validate it rather than skipping it. - if [ "$had_operand" = 0 ]; then - case "$t" in -?*) continue ;; esac - fi - fi - had_operand=1 - # No wildcard in a non-final path segment (`a/*/b`): it can expand through a symlink - # realpath can't see. A slashless glob (`*.rs`) is a final-segment match — fine. - case "$t" in */*) case "${t%/*}" in *[*?[]*) defer "glob in a non-final segment of \`$t\`" ;; esac ;; esac - case "$t" in - /*) canon=$(realpath -m -- "$t" 2>/dev/null) ;; - *) canon=$(realpath -m -- "${cwd:-$PWD}/$t" 2>/dev/null) ;; +split_segments "$cmd" +seg_cwd="${cwd:-$PWD}" +alt_cwd="" # where a `cd` that failed would have left the command +saw_cd=0 +proved=0 # at least one `rm` segment came out auto-allowable +only_ours=1 # ... and nothing else shares the command line + +for seg in "${SEGMENTS[@]}"; do + segment_tokens "$seg" + case "${SEG_TOKS[0]:-}" in + "") continue ;; + rm) + check_rm_segment + proved=1 + continue + ;; + cd) + # A `cd` writes nothing, so it never blocks an allow; it only moves where a later relative + # operand points, to one of the two candidates `apply_cd` describes. + if [ "$saw_cd" = 0 ] && new_cwd=$(apply_cd "$seg_cwd" "${SEG_TOKS[@]:1}"); then + alt_cwd="$seg_cwd" + seg_cwd="$new_cwd" + else + # Not the harmless segment an allow assumes: whatever this guard could not account for + # may be a redirect, and a redirect writes. Leave the line to the normal flow. + seg_cwd="" alt_cwd="" + only_ours=0 + fi + saw_cd=1 + continue + ;; esac - [ -n "$canon" ] || defer "cannot resolve \`$t\`" - # A glob may auto-allow only under /tmp, where everything is deletable. Elsewhere its - # expansion could match `.git`, a dotfile like `.*`, or a nested checkout root that the - # literal-path checks never see — so require literal operands in git repos. - case "$t" in *[*?[]*) case "$canon" in /tmp/?*) ;; *) defer "glob \`$t\` is outside /tmp" ;; esac ;; esac - allowed_target "$canon" || defer "\`$canon\` is outside /tmp and not inside a git checkout in \$HOME" + # Some other command shares the line. If an `rm` runs inside it after all — behind a wrapper, + # an env prefix or a path — this guard cannot say what it deletes. + segment_runs_verb rm "$seg" && defer "rm is not the leading command word in \`$seg\`" + only_ours=0 done -[ "$had_operand" = 1 ] || defer "no operand" -decide allow 'rm operands are under /tmp or inside a git checkout in $HOME' +[ "$proved" = 1 ] || exit 0 +[ "$only_ours" = 1 ] && decide allow 'rm operands are under /tmp or inside a git checkout in $HOME' +exit 0 diff --git a/.claude/hooks/lib-guarded-verb.sh b/.claude/hooks/lib-guarded-verb.sh index c4d5f5ac9a..6ef76a5466 100644 --- a/.claude/hooks/lib-guarded-verb.sh +++ b/.claude/hooks/lib-guarded-verb.sh @@ -10,17 +10,6 @@ # expand a glob operand against the filesystem. Neither guard relies on pathname expansion. set -f -# 0 iff ($1) runs as a command word anywhere in ($2). Mirrors how a Bash -# permission rule matches, so that owning the prompt here doesn't narrow what used to prompt: -# the command splits on `; & |` and newlines, and a leading env assignment or process wrapper -# (`timeout 5 rm`, `xargs rm`) is skipped before the command word is read. -# -# The split set also carries the characters that open a nested command — `$(`, backticks and -# `( )` — because a rule matches the verb inside one (`echo $(rm -rf ~)` prompts), and a -# separator that only ends statements would read that as an `echo`. Braces are handled as -# words rather than separators, since splitting on them cuts `xargs -I {} … rm` in half and -# strands the `rm` in a segment that no longer knows a wrapper preceded it. - # 0 iff ($1) starts with a command that only reads its input. An allowlist, because the # opposite — naming the shells to avoid — would have to be complete: an unlisted one (`ash`, # `rbash`, `busybox sh`) executes the body while the guard calls it data. Unrecognized here only @@ -115,35 +104,159 @@ strip_heredoc_bodies() { done } +# 0 iff ($1) runs as a command word in ($2), which must already be one +# segment (no separator left in it). Wrapper, env-prefix and `/bin/` forms all count. +segment_runs_verb() { + local verb="$1" w wrapped=0 + for w in $2; do + # The shell strips quotes and backslashes before it looks up the command, so `'rm'` and + # `r\m` run rm and have to compare equal to it. + w="${w//[\"\'\\]/}" + case "$w" in + "$verb" | */"$verb") return 0 ;; + *=*) ;; # leading env assignment + -* | *'>'* | *'<'*) ;; # a flag, or a leading redirect + [0-9]*) [ "$wrapped" = 1 ] || break ;; # a wrapper's duration, not `1:` in prose + '!' | '{' | '}' | if | then | elif | else | while | until | do) ;; # never the command + timeout | time | nice | nohup | stdbuf | command | builtin | noglob | xargs | sudo | env) + wrapped=1 ;; + # A wrapper's option value is indistinguishable from a command name (`stdbuf -o L rm`), + # so past a wrapper the scan runs to the end of the segment instead of stopping at the + # first ordinary word. Before one, that word is the command and the verb cannot follow + # it. Nothing bounds the scan: a wrapper takes unboundedly many operands + # (`env -u A -u B ...`), and any cutoff — a word count, or stopping at the first quoted + # word — drops the prompt for a real `sudo -u 'root' rm`. Prose after a wrapper is the + # price, and it only over-prompts. + *) [ "$wrapped" = 1 ] || break ;; + esac + done + return 1 +} + +# Splits ($1) into its command segments, into the global array SEGMENTS. Every guard +# reasons one segment at a time, so `a && b` is two commands here rather than one unparsable +# blob, and a newline is a separator like any other. +# +# The split set carries more than `; & |` and newlines: `$(`, backticks and `( )` open a nested +# command, and a separator that only ended statements would read `echo $(rm -rf ~)` as an +# `echo`. Braces are handled as words rather than separators, since splitting on them cuts +# `xargs -I {} … rm` in half and strands the `rm` in a segment that no longer knows a wrapper +# preceded it. +# +# `tr` and not `${1//[...]}`: a `}` inside the bracket expression closes the expansion itself, +# which silently leaves the command unsplit and every separator unseen. +split_segments() { + local seg + SEGMENTS=() + while IFS= read -r seg; do SEGMENTS+=("$seg"); done <<< "$(strip_heredoc_bodies "$1" | tr ';&|()`' '\n')" +} + +# 0 iff ($1) carries a command substitution outside a heredoc body. A substitution is +# concatenated into the word it sits in, and splitting on its opener cuts that word in half: +# `/tmp/a/`printf ../../etc`` would be proved as `/tmp/a/`, with the traversal validated as an +# unrelated segment. Nothing here can evaluate it, so a guard proves nothing about such a +# command. Heredoc bodies are excepted — those are data the split has already dropped. +has_substitution() { + case "$(strip_heredoc_bodies "$1")" in + *'$('* | *'`'*) return 0 ;; + esac + return 1 +} + +# Reads ($1) into the global array SEG_TOKS, dropping the shell keywords that can +# precede a command word so that `then rm -rf x` is analyzed as the `rm` it runs. Word +# splitting only: quotes are left in the token and fail the guards' charset check downstream, +# which is what keeps `rm -rf "$HOME/x"` unprovable. +segment_tokens() { + SEG_TOKS=() + read -r -a SEG_TOKS <<< "$1" + while [ "${#SEG_TOKS[@]}" -gt 0 ]; do + case "${SEG_TOKS[0]}" in + '!' | '{' | '}' | if | then | elif | else | while | until | do) SEG_TOKS=("${SEG_TOKS[@]:1}") ;; + *) break ;; + esac + done +} + +# Prints the directory a `cd` lands in, given the current one ($1) and the tokens after the +# `cd` ($2...). Fails, printing nothing, when the destination cannot be resolved — a variable, +# `-`, an option, a relative path, no operand at all (`cd` alone is $HOME), or more than one. +# +# Resolving says nothing about whether the `cd` will SUCCEED: the destination may not exist, and +# `;` runs the next command anyway, leaving it in the directory it started in. So a caller may +# never treat this as the working directory outright — it is one of two candidates, and a +# relative operand has to be provable against the one the command started in as well. That also +# makes a `cd` word splitting invented out of quoted text harmless: it can only add a candidate, +# never drop one. Past the first `cd` the branching outruns two candidates, so a caller that +# sees a second gives up on relative operands entirely. +apply_cd() { + local cwd="$1" t + shift + [ "$#" -eq 1 ] || return 1 + t="$1" + [ -n "$(printf '%s' "$t" | tr -d 'A-Za-z0-9._/-')" ] && return 1 + # Absolute only. A relative destination is not `$cwd/$t`: the shell searches $CDPATH first, + # so `cd ssh` may land in /etc/ssh, and this cannot see the caller's $CDPATH to rule it out. + case "$t" in /*) ;; *) return 1 ;; esac + realpath -m -- "$t" 2>/dev/null +} + +# Prints the class of a canonical path and returns 0: `tmp` for one strictly under /tmp, or +# `repo:` for one strictly inside the git working tree at , itself under $HOME. +# Fails, printing nothing, for anything else — those are the only roots the guards are willing +# to touch unprompted. The root is part of the class so that a caller pairing two operands can +# tell one checkout from another: sibling repos are separate permission boundaries, not one. +# +# The `repo` class trades on "this is a project under version control" being lower-stakes than +# the same act elsewhere — NOT on full recoverability: committed content is restorable via git, +# but untracked / .gitignore'd / uncommitted content, and an independent nested repo's history +# under a recursively-deleted parent, are NOT. Accepted as a deliberate convenience tradeoff. +# +# The walk stops at $HOME, so a dotfiles repo at ~ can't put all of $HOME in a class, and +# top-level ~ files stay out of one. A working tree's own root folder counts only when it is a +# linked worktree, whose `.git` is a pointer file so the history lives in the main repo and +# survives; a primary checkout's `.git` is a directory holding the history itself, so losing it +# is unrecoverable. +# +# Some paths are in no class in any root, /tmp included. Git history, and the agent's own guards +# and settings, because removing those is what removes the prompt on everything else. And every +# path `.claude/settings.json` refuses to read — `.env`, `secrets/`, `*.pem`, `*.key`, +# `credentials.json`, `.secret*` — because a `cp` or `mv` that is auto-allowed on both ends +# would rename one out of those globs and hand back through `Read` exactly what they deny. +path_class() { + local canon="$1" d root="" + case "$canon" in + *"/.git" | *"/.git/"* | *"/.claude" | *"/.claude/"*) return 1 ;; + *"/.env" | *"/.env."*) return 1 ;; + *"/secrets" | *"/secrets/"*) return 1 ;; + *.pem | *.key | *"/credentials.json") return 1 ;; + *"/.secret"* | *.secret | *.secrets) return 1 ;; + esac + case "$canon" in /tmp/?*) printf 'tmp'; return 0 ;; esac + [ -n "${HOME:-}" ] || return 1 + case "$canon" in "$HOME"/?*) ;; *) return 1 ;; esac + d="$canon" + while [ "$d" != "/" ] && [ "$d" != "$HOME" ]; do + [ -e "$d/.git" ] && { root="$d"; break; } + d=$(dirname "$d") + done + [ -n "$root" ] || return 1 # not inside a git working tree under $HOME + if [ "$canon" = "$root" ]; then + [ -f "$root/.git" ] || return 1 + fi + printf 'repo:%s' "$root" +} + +# 0 iff ($1) runs as a command word anywhere in ($2). Mirrors how a Bash +# permission rule matches, so that owning the prompt here doesn't narrow what used to prompt: +# a guard consults this before it starts proving segments, and every bail-out it then takes +# is a prompt for exactly the commands a rule would have caught. runs_verb() { - local verb="$1" seg w wrapped - while IFS= read -r seg; do - wrapped=0 - for w in $seg; do - # The shell strips quotes and backslashes before it looks up the command, so `'rm'` and - # `r\m` run rm and have to compare equal to it. - w="${w//[\"\'\\]/}" - case "$w" in - "$verb" | */"$verb") return 0 ;; - *=*) ;; # leading env assignment - -* | *'>'* | *'<'*) ;; # a flag, or a leading redirect - [0-9]*) [ "$wrapped" = 1 ] || break ;; # a wrapper's duration, not `1:` in prose - '!' | '{' | '}' | if | then | elif | else | while | until | do) ;; # never the command - timeout | time | nice | nohup | stdbuf | command | builtin | noglob | xargs | sudo | env) - wrapped=1 ;; - # A wrapper's option value is indistinguishable from a command name (`stdbuf -o L rm`), - # so past a wrapper the scan runs to the end of the segment instead of stopping at the - # first ordinary word. Before one, that word is the command and the verb cannot follow - # it. Nothing bounds the scan: a wrapper takes unboundedly many operands - # (`env -u A -u B …`), and any cutoff — a word count, or stopping at the first quoted - # word — drops the prompt for a real `sudo -u 'root' rm`. Prose after a wrapper is the - # price, and it only over-prompts. - *) [ "$wrapped" = 1 ] || break ;; - esac - done - # `tr` and not `${2//[...]}`: a `}` inside the bracket expression closes the expansion - # itself, which silently leaves the command unsplit and every separator unseen. - done <<< "$(strip_heredoc_bodies "$2" | tr ';&|()`' '\n')" + local verb="$1" seg + split_segments "$2" + for seg in "${SEGMENTS[@]}"; do + segment_runs_verb "$verb" "$seg" && return 0 + done return 1 } diff --git a/.claude/hooks/test-hooks.sh b/.claude/hooks/test-hooks.sh index 65631b6a48..eca946baf9 100644 --- a/.claude/hooks/test-hooks.sh +++ b/.claude/hooks/test-hooks.sh @@ -4,6 +4,10 @@ # What this pins is the `ask` column: a matcher change that turns one into a no-decision drops # that command's only prompt (see lib-guarded-verb.sh). The wrapper, nested-command and quoted # rows are the ones that catch it. +# +# The `allow` column carries its own weight, because a decision covers the whole command line: +# `allow` may only appear where every segment was proved here, and a line that also runs +# something unexamined has to come out `none` so the normal permission flow still sees it. set -uo pipefail H="$(cd "${BASH_SOURCE[0]%/*}" && pwd)" CWD="$(git -C "$H" rev-parse --show-toplevel)" @@ -46,10 +50,11 @@ run $G ask "rm -rf $CWD/*" run $G ask "rm -rf /etc/passwd" run $G ask 'rm -rf "$HOME/x"' run $G ask "rm -rf /tmp/../$OUT" -run $G ask "ls /tmp && rm -rf /tmp/x" +run $G none "ls /tmp && rm -rf /tmp/x" # proved delete, unexamined neighbour run $G ask 'echo $(rm -rf /etc)' run $G ask 'echo `rm -rf /etc`' run $G ask "{ rm -rf /etc; }" +run $G allow "{ rm -rf /tmp/scratch/x; }" # the keyword drops, the delete still proves run $G ask "find . -name x | xargs rm" run $G ask "timeout 5 rm -rf /tmp/x" run $G ask "stdbuf -o L rm -rf /etc" @@ -107,6 +112,33 @@ run $G none 'echo $(ls /tmp)' run $G none 'grep -rn "rm" backend/' run $G none "cargo build --release" +# Chaining and line breaks are not themselves a reason to prompt: each segment is proved on its +# own operands, and a `cd` moves where a relative one points. +run $G allow "rm -f /tmp/a; rm -rf /tmp/b" +run $G allow "$(printf 'rm -f /tmp/a\nrm -rf %s/frontend/scratch' "$CWD")" +run $G allow "cd /tmp/scratch && rm -rf sub" +run $G none "mkdir -p /tmp/x && rm -rf /tmp/x" +run $G ask "$(printf 'ls /tmp\nrm -rf /etc')" +# A `cd` this guard can resolve is where the relative operand lands; one it cannot leaves the +# working directory unknown, and an unknown one proves nothing. +run $G ask "cd /etc && rm -rf foo" +run $G ask 'cd "$D" && rm -rf foo' +run $G ask "cd $CWD && rm -rf .git" +run $G ask "cd /etc && cd /tmp/scratch && rm -rf sub" # a cd out is not walked back +# A `cd` can fail at runtime, and `;` runs the delete from where the command started, so a +# relative operand is proved from both directories. +run $G ask "cd /tmp/does-not-exist; rm -rf .git" +run $G ask "cd /tmp/does-not-exist; rm -rf backend/.env" +run $G ask "cd /tmp/a && cd /tmp/b && rm -rf sub" +run $G ask "rm -rf /tmp/clone/.git" # history is never in a class +run $G ask "rm -rf /tmp/scratch/id_rsa.key" +run $G none "cd /tmp >$OUT; rm -f /tmp/a" +# A substitution is concatenated into its word, so splitting on it would prove only the literal +# half; a relative `cd` is not $cwd/$t either, since the shell searches $CDPATH first. +run $G ask 'rm -rf /tmp/a/`printf ../../etc`' +run $G ask 'rm -rf /tmp/a/$(printf ../../etc)' +run $G ask "cd ssh && rm -rf moduli" + echo echo "== allow-fileops-in-tmp.sh ==" A=allow-fileops-in-tmp.sh @@ -117,7 +149,7 @@ run $A allow "tar -xzf /tmp/a.tar.gz -C /tmp/out" run $A ask "mv /tmp/a $OUT" run $A ask "mv $CWD/AGENTS.md /tmp/a" run $A ask "chmod -R 777 $CWD" -run $A ask "ls && mv /tmp/a /tmp/b" +run $A none "ls && mv /tmp/a /tmp/b" # proved move, unexamined neighbour run $A ask 'echo $(mv /tmp/a /etc)' run $A ask "timeout --signal KILL 5 mv /tmp/a /etc" run $A ask "time -f FORMAT chmod 777 $OUT" @@ -129,5 +161,49 @@ run $A none "cp $CWD/AGENTS.md /tmp/a" run $A none "tar -xzf /tmp/a.tar.gz -C $OUT" run $A none "cargo build" +run $A none "mkdir -p /tmp/x; mv /tmp/a /tmp/x; chmod 755 /tmp/x" # one write per line +run $A none "$(printf 'mv /tmp/a /tmp/b\nchmod 755 /tmp/b')" +run $A ask "ls && mv /tmp/a /etc" +run $A ask "$(printf 'mkdir -p /tmp/x\nchmod -R 777 %s' "$CWD")" +run $A allow "cd /tmp/x && tar -xzf /tmp/a.tar.gz -C /tmp/out" +# The checkout is a root of its own, so an in-repo move or chmod is as auto-allowable as the +# in-repo delete already was — but one operation may not straddle it and /tmp. +run $A allow "chmod +x scripts/worktree-env" +run $A allow "mv backend/.sqlx backend/.sqlx.bad" +run $A allow "mv $CWD/frontend/a.ts $CWD/frontend/b.ts" +run $A ask "mv /tmp/a $CWD/frontend/a.ts" +run $A ask "chmod -R 777 $CWD/.git" +run $A ask "mv $CWD/backend/.env $CWD/backend/.env.bak" +run $A ask "mv $CWD/AGENTS.md $OUT" +run $A ask "cd /etc && mv a b" +# An auto-allowed rename may not carry a path out of the `Read` deny globs. +run $A ask "mv backend/server.pem backend/server.txt" +run $A none "cp backend/secrets/token frontend/token.txt" # cp has no prompt of its own, + # so what matters is it is not allowed +run $A ask "mv $CWD/backend/credentials.json /tmp/x" +run $A ask "cd /tmp/does-not-exist; mv .claude/settings.json settings.bak" +# A segment this hook cannot read whole may carry a redirect, and an earlier write can change +# what a later operand resolves to — neither may ride along on an allow. +run $A none "cd /tmp >$OUT; mv /tmp/a /tmp/b" +run $A none "cp -r /tmp/tree /tmp/live; cp /tmp/payload /tmp/live/link" +run $A ask 'mv /tmp/a/`printf ../../etc/x` /tmp/b' +# A sibling checkout is a different root: its files are outside what the Read tool is confined +# to, and copying them in would hand back what that confinement withholds. +EE="$(dirname "$CWD")/windmill-ee-private" # a sibling checkout; absent elsewhere, still not a root +run $A ask "mv $EE/backend/x.rs $CWD/backend/x.rs" +run $A none "cp $EE/README.md $CWD/README.copy" +# Directory form writes a path the command does not name — DEST/basename(SRC) — and `cp` +# follows that child when it is a symlink, as every `*_ee.rs` in this checkout is. +run $A ask "mv frontend/apps_ee.rs backend/windmill-api/src" +run $A none "cp frontend/apps_ee.rs backend/windmill-api/src" +run $A none "cp frontend/a.ts backend" +run $A ask "mv /tmp/a $CWD/backend" +# ... and a `cd` that fails at runtime may not hide that form: the destination is a directory +# in the directory the command actually ran in, whichever of the two that turns out to be. +run $A none "cd $CWD/AGENTS.md; cp frontend/apps_ee.rs backend/windmill-api/src" +run $A ask "cd $CWD/AGENTS.md; mv frontend/apps_ee.rs backend/windmill-api/src" +run $A none "cd /tmp/x && tar -xzf /tmp/a.tar.gz" # no -C, and the cwd is now two candidates +run $A allow "cp frontend/a.ts backend/a.ts" # ... naming the destination proves fine + echo [ "$fails" = 0 ] && echo "ALL PASS" || { echo "$fails FAILURES"; exit 1; } diff --git a/AGENTS.md b/AGENTS.md index 06b0402fd6..1da4be6c98 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -147,10 +147,19 @@ $NAV --root backend callees "X" # what does X call? - **MUST `outline` before `Read`** on unfamiliar files — then `body` or `Read` with offset/limit for specifics - **Scratch stays outside the checkout.** Temp scripts, data dumps, cache backups and screenshots go in the session scratch directory or `/tmp`, so nothing temporary can end up - committed. Write `rm`/`mv`/`cp` as one plain unchained command: a PreToolUse hook - auto-allows those when every operand is under `/tmp` or inside this checkout, but it defers - on `&&`, `;`, redirects, quotes and `$VAR` — that deferral, not the delete itself, is what - turns a routine cleanup into a permission prompt. + committed. Write the paths in `rm`/`mv`/`cp` out literally: a PreToolUse hook proves each + operand, and auto-allows deletes, moves, copies and mode changes under `/tmp` or inside a git + checkout under `$HOME`, as long as one operation stays within a single root — a sibling + checkout is a root of its own (`tar` and `unzip` stay `/tmp`-only). Chain deletes freely, each + proved on its own operands, but keep writes to one per line, name the destination rather than + a directory to drop it in, and put anything else on its own line: a command the hook does not + prove drops the whole line back to the normal permission flow. A + quoted or `$VAR` operand, a `~`, a redirect, a `$(…)`, a relative `cd`, or a wrapper like + `xargs rm` cannot be proved, and that deferral is what turns a cleanup into a prompt. +- **Change files with Edit/Write, not the shell.** `sed -i`, `cat > file <<'EOF'` and inline + `python3 - <<'PY'` scripts put an edit through the PreToolUse guards and the permission + classifier, which match `Bash` and nothing else, so a routine edit arrives as a prompt. Bash + stays right for running things — tests, builds, git, one-off queries. - Search for existing code to reuse before writing new code - Follow established patterns in the codebase - Keep changes focused — don't refactor beyond what's asked From 6749015fbf7afe0c6dcd53b1933b0152915afd32 Mon Sep 17 00:00:00 2001 From: Guilhem Date: Tue, 18 Aug 2026 12:25:21 +0200 Subject: [PATCH 47/60] fix: audit the icon library against brand guidelines (#10722) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: audit the icon library against brand guidelines Every icon component checked against its brand's own published guidelines for correct artwork, current colours, and readability on both app surfaces. - 127 marks now carry a per-theme pair (text-[#light] dark:text-[#dark]), applied only where the brand publishes a reversed or dark variant. twMerge where the component exposes a class prop, so callers can still pass sizing. - 296 of 304 brand icons record their source in a comment above the , including the rule where the brand imposes one (Google forbids recolouring, Cal.com is deliberately greyscale, Oracle reserves the MySQL dolphin). - BRAND_COLORS.md is generated from the components, so the table cannot drift from the code. - Marks that were unreadable on a surface: 13 -> 1 on dark, 9 -> 4 on light. The remainder are blocked by trademark terms, not unfixed. - Wrong artwork replaced where a first-party or CC0 source existed: PayPal is the real three-colour monogram, Stripe is the bare S rather than an app tile, gcloud resolves to Google's mark instead of a generic hexagon. - Concept icons (CACertificate, DbIcon, Webdav, Asset*, Bcrypt) inherit currentColor instead of hardcoding a colour. Fixes a cross-component CSS bug: ten icons embedded
@@ -904,8 +936,8 @@

Instance-configured OAuth APIs

- {#if filteredConnects} - {#each filteredConnects as { key }} + {#if rankedConnects} + {#each rankedConnects as { key }} {/if} {#each filteredResources as r} - {@const isPicked = value === r} + {@const isPicked = value === r.name} {/each} diff --git a/frontend/src/lib/components/common/table/RowIcon.svelte b/frontend/src/lib/components/common/table/RowIcon.svelte index 7ef3543073..9f0395b924 100644 --- a/frontend/src/lib/components/common/table/RowIcon.svelte +++ b/frontend/src/lib/components/common/table/RowIcon.svelte @@ -116,15 +116,15 @@ {:else if effectiveKind === 'postgres'} {:else if effectiveKind === 'kafka'} - + {:else if effectiveKind === 'nats'} - + {:else if effectiveKind === 'mqtt'} - + {:else if effectiveKind === 'amqp'} - + {:else if effectiveKind === 'sqs'} - + {:else if effectiveKind === 'gcp'} {:else if effectiveKind === 'azure'} diff --git a/frontend/src/lib/components/graph/renderers/triggers/TriggersBadge.svelte b/frontend/src/lib/components/graph/renderers/triggers/TriggersBadge.svelte index ac41827d6c..29d12b8084 100644 --- a/frontend/src/lib/components/graph/renderers/triggers/TriggersBadge.svelte +++ b/frontend/src/lib/components/graph/renderers/triggers/TriggersBadge.svelte @@ -5,16 +5,18 @@ import { getContext } from 'svelte' import { type TriggerContext } from '$lib/components/triggers' import { enterpriseLicense } from '$lib/stores' - import { - MqttIcon, - AmqpIcon, - NatsIcon, - KafkaIcon, - AwsIcon, - GoogleCloudIcon - } from '$lib/components/icons' + import MqttIcon from '$lib/components/icons/MqttIcon.svelte' + import AmqpIcon from '$lib/components/icons/AmqpIcon.svelte' + import NatsIcon from '$lib/components/icons/NatsIcon.svelte' + import KafkaIcon from '$lib/components/icons/KafkaIcon.svelte' + import AwsIcon from '$lib/components/icons/AwsIcon.svelte' + import GoogleCloudIcon from '$lib/components/icons/GoogleCloudIcon.svelte' import AzureIcon from '$lib/components/icons/AzureIcon.svelte' - import { type Trigger, type TriggerType } from '$lib/components/triggers/utils' + import { + triggerIconMapMono, + type Trigger, + type TriggerType + } from '$lib/components/triggers/utils' import { Menu, Menubar, MeltButton, MenuItem, Tooltip } from '$lib/components/meltComponents' import { twMerge } from 'tailwind-merge' import SchedulePollIcon from '$lib/components/icons/SchedulePollIcon.svelte' @@ -320,10 +322,13 @@ {/snippet} {#snippet simpleTriggerItem({ item, type })} - {@const { icon: SvelteComponent, countKey } = triggerTypeConfig()[type] || { + {@const { icon: ColourIcon, countKey } = triggerTypeConfig()[type] || { icon: Database, countKey: undefined }} + + {@const SvelteComponent = triggerIconMapMono[type] ?? ColourIcon}
diff --git a/frontend/src/lib/components/icons/AblyIcon.svelte b/frontend/src/lib/components/icons/AblyIcon.svelte new file mode 100644 index 0000000000..ce4c360152 --- /dev/null +++ b/frontend/src/lib/components/icons/AblyIcon.svelte @@ -0,0 +1,59 @@ + + + + diff --git a/frontend/src/lib/components/icons/AbstractApiIcon.svelte b/frontend/src/lib/components/icons/AbstractApiIcon.svelte new file mode 100644 index 0000000000..a4b1635775 --- /dev/null +++ b/frontend/src/lib/components/icons/AbstractApiIcon.svelte @@ -0,0 +1,30 @@ + + + + + + + + + + + diff --git a/frontend/src/lib/components/icons/AcceloIcon.svelte b/frontend/src/lib/components/icons/AcceloIcon.svelte new file mode 100644 index 0000000000..0f7846e903 --- /dev/null +++ b/frontend/src/lib/components/icons/AcceloIcon.svelte @@ -0,0 +1,23 @@ + + + + + + diff --git a/frontend/src/lib/components/icons/ActimoIcon.svelte b/frontend/src/lib/components/icons/ActimoIcon.svelte new file mode 100644 index 0000000000..59accffb99 --- /dev/null +++ b/frontend/src/lib/components/icons/ActimoIcon.svelte @@ -0,0 +1,23 @@ + + + + diff --git a/frontend/src/lib/components/icons/ActiveCampaignIcon.svelte b/frontend/src/lib/components/icons/ActiveCampaignIcon.svelte index 836b5a242b..8d0b08374b 100644 --- a/frontend/src/lib/components/icons/ActiveCampaignIcon.svelte +++ b/frontend/src/lib/components/icons/ActiveCampaignIcon.svelte @@ -7,9 +7,17 @@ let { height = '24px', width = '24px' }: Props = $props() - + + diff --git a/frontend/src/lib/components/icons/ActivitypubIcon.svelte b/frontend/src/lib/components/icons/ActivitypubIcon.svelte index 0185d98f3a..b8eea782f7 100644 --- a/frontend/src/lib/components/icons/ActivitypubIcon.svelte +++ b/frontend/src/lib/components/icons/ActivitypubIcon.svelte @@ -1,12 +1,13 @@ + + interface Props { + height?: string + width?: string + } + + let { height = '24px', width = '24px' }: Props = $props() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/lib/components/icons/AdRapidIcon.svelte b/frontend/src/lib/components/icons/AdRapidIcon.svelte new file mode 100644 index 0000000000..443b96a548 --- /dev/null +++ b/frontend/src/lib/components/icons/AdRapidIcon.svelte @@ -0,0 +1,22 @@ + + + + diff --git a/frontend/src/lib/components/icons/AdhookIcon.svelte b/frontend/src/lib/components/icons/AdhookIcon.svelte new file mode 100644 index 0000000000..89da89b9cf --- /dev/null +++ b/frontend/src/lib/components/icons/AdhookIcon.svelte @@ -0,0 +1,25 @@ + + + + + + + diff --git a/frontend/src/lib/components/icons/AdobeAcrobatSignIcon.svelte b/frontend/src/lib/components/icons/AdobeAcrobatSignIcon.svelte index 2f04246966..f1b513be98 100644 --- a/frontend/src/lib/components/icons/AdobeAcrobatSignIcon.svelte +++ b/frontend/src/lib/components/icons/AdobeAcrobatSignIcon.svelte @@ -1,24 +1,24 @@ - - - - - + + diff --git a/frontend/src/lib/components/icons/AeroWorkflowIcon.svelte b/frontend/src/lib/components/icons/AeroWorkflowIcon.svelte new file mode 100644 index 0000000000..c104bca866 --- /dev/null +++ b/frontend/src/lib/components/icons/AeroWorkflowIcon.svelte @@ -0,0 +1,21 @@ + + + + diff --git a/frontend/src/lib/components/icons/AgentInstructionsIcon.svelte b/frontend/src/lib/components/icons/AgentInstructionsIcon.svelte new file mode 100644 index 0000000000..e5a6169bbe --- /dev/null +++ b/frontend/src/lib/components/icons/AgentInstructionsIcon.svelte @@ -0,0 +1,27 @@ + + + + + + + + diff --git a/frontend/src/lib/components/icons/Ai21Icon.svelte b/frontend/src/lib/components/icons/Ai21Icon.svelte new file mode 100644 index 0000000000..600fb8a3b6 --- /dev/null +++ b/frontend/src/lib/components/icons/Ai21Icon.svelte @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/frontend/src/lib/components/icons/AiAgentIcon.svelte b/frontend/src/lib/components/icons/AiAgentIcon.svelte new file mode 100644 index 0000000000..f3d042c98d --- /dev/null +++ b/frontend/src/lib/components/icons/AiAgentIcon.svelte @@ -0,0 +1,27 @@ + + + + + + + + + + diff --git a/frontend/src/lib/components/icons/AirtableIcon.svelte b/frontend/src/lib/components/icons/AirtableIcon.svelte index e71aa95bdd..70cc6c1698 100644 --- a/frontend/src/lib/components/icons/AirtableIcon.svelte +++ b/frontend/src/lib/components/icons/AirtableIcon.svelte @@ -1,22 +1,31 @@ + + d="m228.6 47.2-190.9 79c-10.6 4.4-10.5 19.5.2 23.7l191.7 76c16.8 6.7 35.6 6.7 52.4 0l191.7-76c10.7-4.2 10.8-19.3.2-23.7L283 47.2c-17.4-7.2-37-7.2-54.4 0" + style="fill:#fcb400" + /> + diff --git a/frontend/src/lib/components/icons/AlgoliaIcon.svelte b/frontend/src/lib/components/icons/AlgoliaIcon.svelte index e003c778e4..5b1fdddedd 100644 --- a/frontend/src/lib/components/icons/AlgoliaIcon.svelte +++ b/frontend/src/lib/components/icons/AlgoliaIcon.svelte @@ -1,12 +1,22 @@ - - + + + diff --git a/frontend/src/lib/components/icons/AmqpIcon.svelte b/frontend/src/lib/components/icons/AmqpIcon.svelte index 41987f704f..c5de0c1699 100644 --- a/frontend/src/lib/components/icons/AmqpIcon.svelte +++ b/frontend/src/lib/components/icons/AmqpIcon.svelte @@ -8,6 +8,8 @@ let { size = 16, color = undefined, class: clazz = '' }: Props = $props() + - - + + diff --git a/frontend/src/lib/components/icons/ApiKeyAuthIcon.svelte b/frontend/src/lib/components/icons/ApiKeyAuthIcon.svelte new file mode 100644 index 0000000000..8fe884c479 --- /dev/null +++ b/frontend/src/lib/components/icons/ApiKeyAuthIcon.svelte @@ -0,0 +1,25 @@ + + + + + + diff --git a/frontend/src/lib/components/icons/ApifyIcon.svelte b/frontend/src/lib/components/icons/ApifyIcon.svelte index b5f1529bdc..9c9e6d5978 100644 --- a/frontend/src/lib/components/icons/ApifyIcon.svelte +++ b/frontend/src/lib/components/icons/ApifyIcon.svelte @@ -1,22 +1,32 @@ + - - - - - - - - - - - + + + + + + + + + + + diff --git a/frontend/src/lib/components/icons/ApolloIcon.svelte b/frontend/src/lib/components/icons/ApolloIcon.svelte index 85dbb1730a..c41d0087f5 100644 --- a/frontend/src/lib/components/icons/ApolloIcon.svelte +++ b/frontend/src/lib/components/icons/ApolloIcon.svelte @@ -1,12 +1,31 @@ - - + + + + + + diff --git a/frontend/src/lib/components/icons/AppwriteIcon.svelte b/frontend/src/lib/components/icons/AppwriteIcon.svelte index 53d8d0369b..34de69ee08 100644 --- a/frontend/src/lib/components/icons/AppwriteIcon.svelte +++ b/frontend/src/lib/components/icons/AppwriteIcon.svelte @@ -1,21 +1,29 @@ + - - + diff --git a/frontend/src/lib/components/icons/ArcGisIcon.svelte b/frontend/src/lib/components/icons/ArcGisIcon.svelte new file mode 100644 index 0000000000..660d28b804 --- /dev/null +++ b/frontend/src/lib/components/icons/ArcGisIcon.svelte @@ -0,0 +1,15 @@ + + + + + + diff --git a/frontend/src/lib/components/icons/AsanaIcon.svelte b/frontend/src/lib/components/icons/AsanaIcon.svelte index 2c03b80a27..e6043784fd 100644 --- a/frontend/src/lib/components/icons/AsanaIcon.svelte +++ b/frontend/src/lib/components/icons/AsanaIcon.svelte @@ -7,6 +7,7 @@ let { height = '24px', width = '24px' }: Props = $props() + Asana diff --git a/frontend/src/lib/components/icons/AssemblyAiIcon.svelte b/frontend/src/lib/components/icons/AssemblyAiIcon.svelte new file mode 100644 index 0000000000..bfc65a32db --- /dev/null +++ b/frontend/src/lib/components/icons/AssemblyAiIcon.svelte @@ -0,0 +1,28 @@ + + + + + + + diff --git a/frontend/src/lib/components/icons/AssetDatabaseIcon.svelte b/frontend/src/lib/components/icons/AssetDatabaseIcon.svelte index b4d019e4b6..d7342a2765 100644 --- a/frontend/src/lib/components/icons/AssetDatabaseIcon.svelte +++ b/frontend/src/lib/components/icons/AssetDatabaseIcon.svelte @@ -6,7 +6,12 @@ class?: string } - let { height = '24px', width = '24px', fill = 'black', class: className = '' }: Props = $props() + let { + height = '24px', + width = '24px', + fill = 'currentColor', + class: className = '' + }: Props = $props() + interface Props { + height?: string + width?: string + } + + let { height = '24px', width = '24px' }: Props = $props() + + + + + + + diff --git a/frontend/src/lib/components/icons/Auth0Icon.svelte b/frontend/src/lib/components/icons/Auth0Icon.svelte index fbf419e3cf..97fa53a3d9 100644 --- a/frontend/src/lib/components/icons/Auth0Icon.svelte +++ b/frontend/src/lib/components/icons/Auth0Icon.svelte @@ -1,4 +1,5 @@ + auth0-svg + + + diff --git a/frontend/src/lib/components/icons/AutheliaIcon.svelte b/frontend/src/lib/components/icons/AutheliaIcon.svelte index 037844b4d3..4878dcb26f 100644 --- a/frontend/src/lib/components/icons/AutheliaIcon.svelte +++ b/frontend/src/lib/components/icons/AutheliaIcon.svelte @@ -1,32 +1,48 @@ - - authelia-svg - - + authelia-svg + + - + - + - + - + - + 1340 25 134 25 437 0 575 -26 150 -80 311 -114 343 -43 41 -103 38 -148 -7z" + /> + diff --git a/frontend/src/lib/components/icons/AuthentikIcon.svelte b/frontend/src/lib/components/icons/AuthentikIcon.svelte index ee52fba6af..e79f18eef1 100644 --- a/frontend/src/lib/components/icons/AuthentikIcon.svelte +++ b/frontend/src/lib/components/icons/AuthentikIcon.svelte @@ -1,27 +1,19 @@ - - authentik-svg - - - - - - - - - - - - - - - + + + authentik-svg + + diff --git a/frontend/src/lib/components/icons/AwsEcrIcon.svelte b/frontend/src/lib/components/icons/AwsEcrIcon.svelte index 2801712f62..379a14e4a0 100644 --- a/frontend/src/lib/components/icons/AwsEcrIcon.svelte +++ b/frontend/src/lib/components/icons/AwsEcrIcon.svelte @@ -1,12 +1,16 @@ + + - + - - - - diff --git a/frontend/src/lib/components/icons/AwsIcon.svelte b/frontend/src/lib/components/icons/AwsIcon.svelte index 3b02d9d773..7f371a6229 100644 --- a/frontend/src/lib/components/icons/AwsIcon.svelte +++ b/frontend/src/lib/components/icons/AwsIcon.svelte @@ -1,33 +1,38 @@ + - - diff --git a/frontend/src/lib/components/icons/AzureIcon.svelte b/frontend/src/lib/components/icons/AzureIcon.svelte index c6c3b6d60b..011142b851 100644 --- a/frontend/src/lib/components/icons/AzureIcon.svelte +++ b/frontend/src/lib/components/icons/AzureIcon.svelte @@ -1,22 +1,86 @@ + - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/lib/components/icons/BRAND_COLORS.md b/frontend/src/lib/components/icons/BRAND_COLORS.md new file mode 100644 index 0000000000..1cff77a3d3 --- /dev/null +++ b/frontend/src/lib/components/icons/BRAND_COLORS.md @@ -0,0 +1,578 @@ +# Icon brand colours + +Where every icon's colours come from, and whether they survive both app surfaces. +Compiled from the components themselves during the audit — colours read from the fills and +the Tailwind pair classes, sources from each component's provenance comment, contrast +computed from those hexes against `surface-primary` in each theme. Maintained by hand from +here on: change an icon's colour or source and change its row. + +Surfaces: light `#fbfbfd`, dark `#2e3441`. Ratios are WCAG non-text contrast; **bold** marks a +mark that is effectively invisible on that surface. WCAG exempts logotypes from the 3:1 +floor, so a low ratio is a signal the colour may be wrong, not automatically a defect. + +`pair` = brand publishes a per-theme variant. Usually applied as `text-[#light] dark:text-[#dark]`; `AnsibleIcon` inverts instead (`dark:invert`), and `DatadogIcon`, `DenoIcon`, `DeepLIcon` and `TogglIcon` swap between two SVGs (`dark:hidden` / `hidden dark:block`) because their two marks are different artwork, not the same shape recoloured. +`fixed` = full-colour mark, same in both themes. `inherits` = brand publishes no colour, +so the mark takes the surrounding text colour. `mixed` = the root carries a +`fill="currentColor"` that hardcoded path fills override, so it is inert — these are +candidates for cleanup, not theme-aware icons. + +The Light/Dark columns show the colour that carries the mark; white and black knockout +details are omitted. Ratios are the best contrast any part of the mark achieves. + +**Do not change a colour here without a first-party source.** Several of these look like +mistakes and are not: Cal.com is deliberately greyscale, Google Cloud may not be recoloured, +Stripe is blurple rather than black. Third-party icon sets go stale and have been wrong +repeatedly — check the brand's own page. + +| Icon | Resource types | Mode | Light | Dark | ☀ | 🌙 | Source | +|---|---|---|---|---|---|---|---| +| `AblyIcon` | `ably` | fixed | #FF5416 | #FF5416 | 3.87 | 3.88 | brand.ably.com/logo | +| `AbstractApiIcon` | `abstractapi` | fixed | #20E492 | #20E492 | **1.62** | 12.47 | abstractapi.com's own logo SVG (6538df34291c9fa4ed28d6f7_Logo.svg) | +| `AcceloIcon` | `accelo` | fixed | #4C49CB | #4C49CB | 6.51 | 8.15 | Accelo_Logo-Primary.svg on accelo.com | +| `ActiveCampaignIcon` | `activecampaign` | pair | #004CFF | #FFFFFF | 5.84 | 12.47 | activecampaign.com/brand logo pack (ActiveCampaign-Glyph-Blue.svg / ActiveCampaign-Glyph-White.svg) | +| `ActivitypubIcon` | `activitypub` | fixed | #F1007E | #F1007E | 5.01 | 2.99 | activitypub.rocks/static/images/ActivityPub-logo.svg | +| `AcumbamailIcon` | `acumbamail` | fixed | #E62F71 | #E62F71 | 8.83 | 8.86 | Acumbamail's own isotype SVG, /static/favico/Acumbamail/favicon-32.svg on acumbamail.com | +| `AdhookIcon` | `adhook` | fixed | #00ACC6 | #00ACC6 | 2.63 | 4.58 | adhook's own logo (https://adhook.io/fr/images/logo.svg, `.cls-1{fill:#00acc6}`) | +| `AdobeAcrobatSignIcon` | `adobe_acrobat_sign` | fixed | #584CCC | #584CCC | 6.12 | 12.47 | Adobe's own Acrobat Sign product icon (adobe.com/cc-shared/assets/img/product-icons/svg/acrobat-sign.svg); same value in the live app favicon | +| `Ai21Icon` | `ai21` | pair | #000000 | #FFFFFF | 20.32 | 12.47 | ai21.com (ai21-logo-black.svg / ai21-logo-white.svg) | +| `AirtableIcon` | `airtable` | mixed | #FCB400 | #FCB400 | 3.88 | 6.93 | airtable.com/favicon.ico (fixed full-colour mark: #18BFFF and #F82B60 panels) | +| `AlgoliaIcon` | `algolia` | pair | #003DFF | #FFFFFF | 6.53 | 12.47 | algolia.com logo pack (Algolia-mark-blue.svg / Algolia-mark-white.svg) | +| `AmqpIcon` | `amqp` | fixed | — | — | — | — | — | +| `AnsibleIcon` | `ansible` | pair | #1A1918 | #E5E6E7 | 16.99 | 9.98 | ansible/logos community-marks (Black and White variants, CC BY-SA 4.0) | +| `AnthropicIcon` | `anthropic` | pair | #141413 | #FAF9F5 | 17.84 | 11.84 | anthropics/skills | +| `ApifyIcon` | `apify` | fixed | #246DFF | #246DFF | 4.32 | 12.47 | apify.com/resources/brand | +| `ApolloIcon` | `apollo` | pair | #1F1F1E | #F8FF2C | 15.96 | 11.48 | apollo.io | +| `AppwriteIcon` | `appwrite` | mixed | #FD366E | #FD366E | 3.88 | 5.71 | https://appwrite.io/assets | +| `ArcGisIcon` | `arcgis_account` | fixed | #006FDE | #006FDE | 4.69 | 2.57 | Esri's ArcGIS Pro product logo (esri.com/content/dam/esrisites/en-us/common/icons/product-logos/arcgis-pro-64.svg) | +| `AsanaIcon` | `asana` | fixed | #FF584A | #FF584A | 3.01 | 4.01 | asana.com/brand | +| `AssemblyAiIcon` | `assemblyai` | pair | #1D1B16 | #C7C3B2 | 16.65 | 12.47 | assemblyai.com (assemblyai-logo-full-primary.svg / assemblyai-logo-full-secondary.svg) | +| `AttioIcon` | `attio` | pair | #1C1D1F | #FFFFFF | 16.32 | 12.47 | the attio.com header logo (--color-black-100 / --color-white-100) | +| `Auth0Icon` | `auth0` | pair | #232220 | #FFFFFF | 15.38 | 12.47 | auth0.com docs logo light.svg / dark.svg | +| `AutheliaIcon` | `authelia` | fixed | #3F51B4 | #3F51B4 | 6.67 | 1.81 | authelia.com/images/branding/logo-cropped.svg (light stop of the official #3F51B4→#113155 gradient, flattened) | +| `AuthentikIcon` | `authentik` | pair | #FD4B2D | #FFFFFF | 3.27 | 12.47 | goauthentik.io/press | +| `AwsEcrIcon` | `aws_ecr` | fixed | #ED7100 | #ED7100 | 2.92 | 12.47 | the AWS Architecture Icons package (Icon-package_07312026, Arch_Containers/Arch_Amazon-Elastic-Container-Registry) | +| `AwsIcon` | `aws`, `redshift` | pair | #252F3E | #FF9900 | 13.07 | 5.83 | AWS's own logo files (d0.awsstatic.com/logos/powered-by-aws{,-white}.png) | +| `AzureIcon` | `azure` | fixed | — | — | — | — | Microsoft's own logo_azure.svg (learn.microsoft.com/media/logos/logo_azure.svg), whose outer wedges add the #114A8B->#0669BC and #3CCBF4->#2892DF gradients | +| `BambooHrIcon` | `bamboo_hr` | pair | #599D15 | #FFFFFF | 3.25 | 12.47 | bamboohr.com (Encore --brandColor; bamboohr-logo-white.png is the published reversed variant) | +| `BaremetricsIcon` | `baremetrics` | fixed | #5386FF | #5386FF | 3.27 | 3.70 | the mark in baremetrics.com's header logo (baremetrics-logo.svg), the asset this path is taken from | +| `BaserowIcon` | `baserow`, `baserow_table` | fixed | #2BC3F1 | #2BC3F1 | 4.96 | 6.05 | the baserow.io favicon and horizontal logo | +| `BasisTheoryIcon` | `basis_theory` | pair | #1D2032 | #EBEDFF | 15.57 | 10.74 | developers.basistheory.com/img/bt-logo-light.svg and bt-logo-dark.svg, which ship the same mark geometry in the two theme colours | +| `BeamerIcon` | `beamer` | pair | #1C1E21 | #FFFFFF | 16.16 | 12.47 | the getbeamer.com header logo (g#isotype) and their webclip app icon, which sets the same mark in white on #1C1E21 | +| `BigQueryIcon` | `bigquery` | fixed | #34A853 | #34A853 | 3.80 | 7.30 | Google Cloud's official icon library (cloud.google.com/icons, core-products-icons.zip) | +| `BitbucketIcon` | `bitbucket` | pair | #1868DB | #FFFFFF | 5.03 | 12.47 | atlassian.design/foundations/logos (Bitbucket mark, brand and inverse) | +| `BitlyIcon` | `bitly` | fixed | #F36600 | #F36600 | 3.03 | 3.99 | bitly.com/pages/bitly-logo-usage-guidelines-for-media (Bitly-MediaKit glyph_bitly_orange_RGB.svg) | +| `BloggerIcon` | `blogger` | fixed | #F57C00 | #F57C00 | 2.62 | 12.47 | Google's Blogger product logo (gstatic.com/images/branding/productlogos/blogger/v5/192px.svg) | +| `BlueskyIcon` | `bluesky` | pair | #0560FF | #FFFFFF | 4.93 | 12.47 | bsky.social/about/support/branding | +| `BotifyIcon` | `botify` | fixed | #A973FF | #A973FF | 3.09 | 3.91 | botify.com design tokens (--color--surface--purple-05) | +| `BoxIcon` | `box` | pair | #0061D5 | #FFFFFF | 5.54 | 12.47 | box.com (.box-logo-svg fill:#0061d5, reversed to #fff over the dark masthead) | +| `BrevoIcon` | `brevo`, `sendinblue` | fixed | #0B996E | #0B996E | 3.51 | 12.47 | brevo.com's favicon.svg | +| `BrexIcon` | `brex` | pair | #15191E | #FFFFFF | 17.08 | 12.47 | brex.com | +| `BrowserlessIcon` | `browserless` | pair | #000000 | #FFFFFF | 20.32 | 12.47 | browserless.io/favicon.svg | +| `BubbleIcon` | `bubble` | mixed | #0000FF | #0000FF | 8.31 | 5.71 | the logo SVG served on bubble.io/brand; the B is #262626 there, kept as currentColor so the monochrome part follows the app theme | +| `BuildkiteIcon` | `buildkite` | fixed | #30F2A2 | #30F2A2 | 2.04 | 8.52 | buildkite.com/about/brand-assets | +| `BunIcon` | — | fixed | #FBF0DF | #FBF0DF | 6.48 | 12.47 | https://bun.com/logo.svg | +| `ButtondownIcon` | `buttondown` | fixed | #0069FF | #0069FF | 4.55 | 2.65 | https://buttondown.com/brand | +| `CSharpIcon` | — | fixed | #927BE5 | #927BE5 | 7.68 | 12.47 | dotnet/brand logo/language-icons/csharp-72.svg (CC0) | +| `CalcomIcon` | `calcom` | pair | #292929 | #FAFAFA | 14.08 | 11.95 | design.cal.com | +| `CalendlyIcon` | `calendly` | pair | #006BFF | #FFFFFF | 4.47 | 12.47 | Calendly's 2024 External Brand Guidelines and calendly_brand mark_white.svg (media kit on calendly.com/newsroom) | +| `CampaynIcon` | `campayn` | fixed | #008AFF | #008AFF | 3.34 | 12.47 | app.campayn.com/images/campayn/favicons/safari-pinned-tab.svg (colours sampled from android-chrome-512x512.png in the same directory) | +| `CertopusIcon` | `certopus` | fixed | #FF6E30 | #FF6E30 | 12.07 | 12.47 | https://certopus.com/images/logo/logo_circle.svg | +| `ChromaIcon` | `chromadb` | fixed | #FFDE2D | #FFDE2D | 3.65 | 9.35 | Chroma's own logo SVG served by trychroma.com (chroma-wordmark.svg) | +| `CircleCiIcon` | `circleci` | pair | #161616 | #FFFFFF | 17.51 | 12.47 | brand.circleci.com | +| `CiscoIcon` | `cisco` | pair | #00BCEB | #FFFFFF | 2.16 | 12.47 | cisco.com logo SVG and newsroom.cisco.com/logos | +| `ClaudeIcon` | — | fixed | #D97757 | #D97757 | 3.02 | 12.47 | https://claude.ai/favicon.svg (Anthropic's own asset) | +| `ClearbitIcon` | `clearbit` | fixed | #4DB1FD | #4DB1FD | 20.32 | 10.83 | clearbit.com/logo.svg | +| `ClerkIcon` | `clerk` | fixed | #BAB1FF | #BAB1FF | 5.10 | 6.43 | clerk.com/brand-assets (symbol-primary.svg) | +| `ClickhouseIcon` | `clickhouse` | pair | #161616 | #FFFFFF | 17.51 | 12.47 | clickhouse.design/brand/logo-usage (logomark, on-light / on-dark) | +| `ClickupIcon` | `clickup` | fixed | #6647F0 | #6647F0 | 5.46 | 4.12 | clickup.com/brand (v4 Logomark-gradient.svg); the gradient mark is the same on light and dark, and the guidelines say "don't change the color" | +| `CloseIcon` | `close` | fixed | #4EC375 | #4EC375 | 4.77 | 7.39 | close.com/brand (close-logo-2024 mark.svg) | +| `CloudflareIcon` | `cloudflare` | fixed | #FF5F08 | #FF5F08 | 2.95 | 5.83 | the logomark shipped on cloudflare.com, blog.cloudflare.com and workers.cloudflare.com | +| `CloudinaryIcon` | `cloudinary` | pair | #3448C5 | #FFFFFF | 7.06 | 12.47 | cloudinary_logo_for_white_bg.svg and cloudinary_logo_for_black_bg.svg on cloudinary-res.cloudinary.com | +| `CockroachDbIcon` | `cockroachdb` | pair | #6933FF | #FFFFFF | 5.78 | 12.47 | cockroachlabs.com (electric-purple-500, also the CockroachDB docs primaryColor) and the docs light/dark logo pair | +| `CodaIcon` | `coda` | fixed | #F46A54 | #F46A54 | 2.89 | 4.18 | Coda's own app icon, https://cdn.coda.io/icons/png/color/coda-192.png (single-colour mark, no dark variant published) | +| `CodatIcon` | `codat` | fixed | #D1E100 | #D1E100 | 17.31 | 8.60 | codat.io (logo-white.svg glyph outlines, colours from the site palette); framing matches their 300x300 favicon exactly | +| `CohereIcon` | `cohere` | fixed | #355146 | #355146 | 8.41 | 12.47 | https://cohere.com/logo.svg | +| `CoinMarketCapIcon` | `coinmarketcap` | pair | #000000 | #FFFFFF | 20.32 | 12.47 | coinmarketcap.com | +| `CoinbaseIcon` | `coinbase` | pair | #0052FF | — | 5.57 | — | Coinbase's own light/dark logo files (mintcdn.com/coinbase-prod/.../logos/wordmark-light.svg and wordmark-dark.svg, served by docs.cdp.coinbase.com) | +| `ComapeoIcon` | `comapeo_server` | pair | #022199 | #0066FF | 12.09 | 2.58 | the CoMapeo Cloud mark shipped as public/favicon.svg in digidem/comapeo-cloud-app (the server this resource connects to) | +| `ConfluenceIcon` | `confluence` | fixed | #1868DB | #1868DB | 5.03 | 12.47 | Atlassian's @atlaskit/logo (atlassian.design logo library) | +| `ContentfulIcon` | `contentful` | fixed | #1773EB | #1773EB | 4.33 | 9.08 | Contentful's Forma 36 design system (ContentfulLogoIcon) | +| `ContiguityIcon` | `contiguity` | pair | #000000 | #FFFFFF | 20.32 | 12.47 | contiguity.com/assets/icon-white.png and icon-black.png (docs.contiguity.com likewise ships logo/black.svg for light and logo/white.svg for dark) | +| `ConvertKitIcon` | `convertkit` | pair | #1E1E1E | #F2EFE9 | 16.13 | 10.87 | kit.com/brand | +| `CoupaIcon` | `coupa` | pair | #1565C0 | #FFFFFF | 5.56 | 12.47 | the Coupa logo kit linked from coupa.com/company/press-kit, which ships the mark in blue and a white reversed variant | +| `CssIcon` | — | fixed | #663399 | #663399 | 8.13 | 12.47 | github.com/CSS-Next/logo.css (CC0), the official CSS logo endorsed by the W3C CSS WG | +| `CurrencyApiIcon` | `currencyapi` | fixed | #2994FF | #2994FF | 9.13 | 4.67 | currencyapi.com/img/currencyapi_logo_color.svg | +| `DatabricksIcon` | `databricks` | fixed | #FF3621 | #FF3621 | 3.50 | 3.45 | Databricks' own logo asset (databricks.com/sites/default/files/2023-08/databricks-default.png) | +| `DatadogIcon` | `datadog` | pair | #632CA6 | #FFFFFF | 8.32 | 12.47 | datadoghq.com press kit | +| `DatoCmsIcon` | `datocms` | fixed | #FF7751 | #FF7751 | 2.54 | 4.76 | datocms.com/company/brand-assets | +| `DbtIcon` | `dbt_profile` | fixed | #FE6703 | #FE6703 | 2.84 | 4.25 | the dbt Labs brand assets (getdbt.com/brand-guidelines) | +| `DeelIcon` | `deel` | pair | #1B1B1B | #FFFFFF | 16.67 | 12.47 | deel.com's own logo_revamp.svg / logo_revamp_white.svg | +| `DeepInfraIcon` | `deep_infra` | pair | #2A3275 | #4C9CEC | 11.22 | 12.47 | the DeepInfra press-kit logo pack (deepinfra.com/media-center → DEEPINFRA_LOGO_COLOR / DEEPINFRA_LOGO_WHITE) | +| `DeepLIcon` | `deepl` | pair | #0F2B46 | #FFFFFF | 13.97 | 12.47 | DeepL's official logo pack on deepl.com/en/press ("Logo Deep Blue" RGB #0F2B46 and the published "Logo White" reversed variant) | +| `DeepSeekIcon` | `deepseek` | pair | #4D6BFE | #6799FE | 4.19 | 4.49 | deepseek.com design tokens (--ds-color-brand under :root / [data-theme=dark]) | +| `DenoIcon` | — | pair | #000000 | #FFFFFF | 20.32 | 12.47 | the "Deno Logo Guidelines 2024" asset pack on deno.com/brand | +| `DigitalOceanIcon` | `digitalocean` | fixed | #0080FF | #0080FF | 3.67 | 3.29 | DigitalOcean's official logo kit (DO_Logo_icon_blue.svg, linked from digitalocean.com/press) | +| `DiscordIcon` | `discord`, `discord_webhook` | mixed | #5865F2 | #5865F2 | 4.46 | 5.71 | https://discord.com/branding | +| `DiscourseIcon` | `discourse` | pair | #000000 | #FFFFFF | 20.32 | 12.47 | discourse.org/brand (discourse-icon.svg / discourse-icon-dark.svg) | +| `DocSpringIcon` | `docspring` | fixed | #3C8EE0 | #3C8EE0 | 3.31 | 12.47 | DocSpring's own logo SVG, docspring.com/assets/logo-text-*.svg | +| `DockerIcon` | — | fixed | #2560FF | #2560FF | 4.84 | 2.49 | Docker's official logo kit (docker.com/company/newsroom/media-resources) | +| `DocusignIcon` | `docusign` | pair | #000000 | #FFFFFF | 20.32 | 12.47 | brand.docusign.com/logo: only the Nexus overlap flips per background, Cobalt #4C00FF and Poppy #FF5252 must not be recoloured | +| `DropboxIcon` | `dropbox` | fixed | #0061FE | #0061FE | 4.91 | 2.46 | brand.dropbox.com/logo and the DIG token dig-color__primary__base | +| `DuckDbIcon` | `duckdb` | pair | #1A1A1A | #FFF100 | 16.84 | 10.59 | duckdb.org/design logo package (DuckDB_icon-lightmode.svg / DuckDB_icon-darkmode.svg) | +| `DucklakeIcon` | — | pair | #1A1A1A | #2EAFFF | 16.84 | 5.16 | duckdb.org | +| `DustIcon` | `dust` | fixed | #FE9C1A | #FE9C1A | 4.04 | 10.64 | dust.tt/home/brand-resources (Dust_LogoSquare.svg from their brand kit) | +| `DynatraceIcon` | `dynatrace` | fixed | #1496FF | #1496FF | 10.01 | 7.83 | Dynatrace brand guidelines (live.standards.site/dynatrace, Dynatrace_mark_color.svg) | +| `EdgeDbIcon` | `edgedb` | fixed | #8FAF24 | #8FAF24 | 2.44 | 4.94 | geldata.com (favicon/apple-touch-icon glyph and its ) | +| `EnodeIcon` | `enode` | pair | #5D770D | #E8E8E1 | 4.94 | 10.13 | enode.com/static/favicon.svg | +| `EventbriteIcon` | `eventbrite` | fixed | #FF5E30 | #FF5E30 | 2.95 | 4.10 | the 2025 Eventbrite press kit logos; the brand publishes no reversed variant | +| `ExaIcon` | `exa` | pair | #0143D9 | #FFFFFF | 7.24 | 12.47 | exa.ai/brand (Exa Brand Assets kit, Logomark Blue/White) | +| `FaunadbIcon` | `faunadb` | pair | #3F00A5 | #604BE9 | 11.58 | 2.19 | Fauna's own VS Code extension icons (fauna/fauna-vscode: icons/fauna.svg for light themes, icons/fauna-light.svg for dark) | +| `FigmaIcon` | `figma` | fixed | #24CB71 | #24CB71 | 4.42 | 5.85 | static.figma.com/app/icon/2/favicon.svg (2025 brand refresh) | +| `FirebaseIcon` | `firebase` | fixed | #FF9100 | #FF9100 | 4.58 | 7.81 | firebase.google.com/brand-guidelines (Logomark_Full Color.svg in firebase-brand-assets.zip) | +| `FlyIcon` | `fly` | pair | #24175B | #FFFFFF | 15.07 | 12.47 | fly.io | +| `FormstackIcon` | `formstack` | fixed | #21B573 | #21B573 | 2.56 | 4.70 | the brand guide at formstack.com/press-kit | +| `FoxentryIcon` | `foxentry` | fixed | #E74600 | #E74600 | 5.09 | 4.14 | foxentry.com/assets/img/logo-foxentry-symbol.svg | +| `FreshdeskIcon` | `freshdesk` | fixed | #20A849 | #20A849 | 3.01 | 12.47 | Freshworks' own product-logo asset (freshdesk-dew.svg, used on freshworks.com/apps) | +| `FrontAppIcon` | `frontapp` | fixed | #A857F1 | #A857F1 | 3.83 | 3.15 | the logo mark front.com ships inline on its own pages; the mark keeps this purple on both light and dark backgrounds | +| `FunkwhaleIcon` | `funkwhale` | mixed | #009FE3 | #009FE3 | 10.69 | 5.71 | www.funkwhale.audio/logos (theme/images/icon.svg) | +| `GSheetsIcon` | `gsheets` | fixed | #009954 | #009954 | 3.57 | 12.47 | Google product logo sheets_2026q3 (gstatic productlogos, used on workspace.google.com/products/sheets) | +| `GcalIcon` | `gcal` | fixed | #BBE2FF | #BBE2FF | 3.40 | 12.47 | Google's own Calendar 2026 product logo, https://www.gstatic.com/images/branding/productlogos/calendar_2026/v2/web/192px.svg (paths verbatim) | +| `GdocsIcon` | `gdocs` | inherits | #718096 | #A9B0BA | 3.88 | 5.71 | Google's own Docs product icon (gstatic.com/images/branding/productlogos/docs_2026/v2/web/192px.svg, served on workspace.google.com/products/docs) | +| `GdriveIcon` | `gdrive` | fixed | #B43333 | #B43333 | 5.87 | 10.05 | https://www.gstatic.com/images/branding/productlogos/drive_2026/v2/web/192px.svg, Google's own product-logo CDN; paths and gradient stops are verbatim | +| `GhostCmsIcon` | `ghostcms` | pair | #15171A | #FFFFFF | 17.38 | 12.47 | docs.ghost.org | +| `GiphyIcon` | `giphy` | fixed | #FFF35C | #FFF35C | 4.76 | 10.83 | GIPHY's own app icon (giphy.com/static/img/icons/apple-touch-icon-180px.png) | +| `GitBookIcon` | `gitbook` | pair | #181C1F | #F2F7F7 | 16.59 | 11.54 | the GitBook-icon-dark / GitBook-icon-light downloads on gitbook.gitbook.io/brand-assets, matching the live gitbook.com favicon | +| `GitIcon` | `git_repository`, `git` | fixed | #F03C2E | #F03C2E | 3.77 | 3.20 | git-scm.com/community/logos (Git-Icon-1788C.svg, logo by Jason Long, CC BY 3.0) | +| `GithubIcon` | `github` | pair | #000000 | #FFFFFF | 20.32 | 12.47 | brand.github.com/foundations/logo | +| `GitlabIcon` | `gitlab` | mixed | #FC6D26 | #FC6D26 | 4.01 | 6.18 | https://design.gitlab.com/brand-design/color (Orange 03p/02p/01p, "colors from our core logo") | +| `GmailIcon` | `gmail` | mixed | #4285F4 | #4285F4 | 5.61 | 7.30 | gstatic.com/images/branding/product/2x/gmail_2020q4_48dp.png | +| `GoogleAiIcon` | `googleai` | fixed | #217BFE | #217BFE | 3.81 | 5.44 | Google's standard Gemini product icon (gstatic.com/images/branding/productlogos/gemini/v1/192px.svg) | +| `GoogleCalendarIcon` | — | fixed | #BBE2FF | #BBE2FF | 3.40 | 12.47 | the Google Calendar 2026 product icon, taken verbatim from https://www.gstatic.com/images/branding/productlogos/calendar_2026/v2/web/192px.svg | +| `GoogleCloudIcon` | `gcloud`, `gcp_service_account` | fixed | #EA4335 | #EA4335 | 3.80 | 7.30 | Google's own product logo asset https://www.gstatic.com/images/branding/product/2x/google_cloud_64dp.png | +| `GoogleDriveIcon` | — | fixed | #B43333 | #B43333 | 5.87 | 10.05 | https://www.gstatic.com/images/branding/productlogos/drive_2026/v2/web/192px.svg (Drive 2026 mark, copied verbatim) | +| `GoogleFormsIcon` | `gforms` | fixed | #969DFF | #969DFF | 5.99 | 12.47 | Google's own Forms product icon at www.gstatic.com/images/branding/productlogos/forms_2026/v2/web/192px.svg | +| `GoogleIcon` | `google`, `gworkspace` | mixed | #4285F4 | #4285F4 | 3.88 | 7.30 | the G mark Google serves in accounts.google.com/gsi/client | +| `GorgiasIcon` | `gorgias` | pair | #000000 | #FFF9F4 | 20.32 | 11.94 | gorgias.com/about-us/style, which ships the symbol as a "Dark"/"Light" pair | +| `GraphqlIcon` | `graphql` | pair | #E10098 | #FFFFFF | 4.37 | 12.47 | graphql.org | +| `GreipIcon` | `greip` | pair | #141C27 | #FFFFFF | 16.59 | 12.47 | docs.greip.io | +| `GristIcon` | `grist` | fixed | #16B378 | #16B378 | 2.62 | 8.25 | getgrist.com/trademark/assets/ | +| `GroqIcon` | `groqai`, `groq` | fixed | #F43E01 | #F43E01 | 3.67 | 12.47 | https://groq.com/favicon.svg | +| `HackernewsIcon` | `hackernews` | fixed | #FF6600 | #FF6600 | 2.84 | 12.47 | news.ycombinator.com/y18.svg | +| `HoldedIcon` | `holded` | fixed | #FD454D | #FD454D | 3.31 | 3.64 | cdn.holded.com/assets/img/brand/holded-logo.svg | +| `HoneybadgerIcon` | `honeybadger` | fixed | #EA5937 | #EA5937 | 3.40 | 3.55 | honeybadger.io/favicon.svg | +| `HtmlIcon` | — | fixed | #E44D26 | #E44D26 | 3.77 | 12.47 | the W3C HTML5 logo, w3.org/html/logo (downloads/HTML5_Logo.svg) | +| `HubspotIcon` | `hubspot` | pair | #FF2F00 | #FFFFFF | 3.59 | 12.47 | hubspot.com | +| `IfsIcon` | `ifs_cloud_oidc` | fixed | #72C9F8 | #72C9F8 | 6.03 | 6.79 | the IFS symbol on ifs.com | +| `IftttIcon` | `ifttt` | pair | #000000 | #FFFFFF | 20.32 | 12.47 | ifttt.com | +| `InkeepIcon` | `inkeep` | fixed | #D5E5FF | #D5E5FF | 2.45 | 9.79 | Inkeep's brand page "Icon Core" (https://inkeep.com/brand) | +| `IntercomIcon` | `intercom` | pair | #000000 | #FFFFFF | 20.32 | 12.47 | intercom.com | +| `IpinfoIcon` | `ipinfo` | pair | #3091CF | #FFFFFF | 3.34 | 12.47 | ipinfo.io logo-positive.svg and logo-negative.svg | +| `JavaIcon` | — | fixed | #007396 | #007396 | 5.22 | 4.93 | Oracle's Java Branding and Licensing Guidelines v21 (oracle.com/a/ocom/docs/java-licensing-logo-guidelines-1908204.pdf) | +| `JavaScriptIcon` | — | fixed | #F7DF1E | #F7DF1E | 20.32 | 9.22 | js.svg in github.com/voodootikigod/logo.js, the origin of the JavaScript logo | +| `JiraIcon` | `jira` | fixed | #1868DB | #1868DB | 5.03 | 12.47 | Atlassian's official Jira logo pack (atlassian.design/foundations/logos) | +| `JoomlaIcon` | `joomla` | fixed | #7AC143 | #7AC143 | 3.58 | 6.23 | the official logo at cdn.joomla.org/images/joomla-colours-logo.svg | +| `JotformIcon` | `jotform` | pair | #0A1551 | #FFFFFF | 16.40 | 12.47 | jotform.com footer logomark (#jotform-logomark-fourth is filled with --jf-logo-img: #0A1551 light, #fff dark) | +| `JsonIcon` | — | fixed | #F9A825 | #F9A825 | 1.91 | 6.33 | Material Design Yellow 800 (api.flutter.dev Colors.yellow[800]); glyph is Google's Material Symbols "data_object" | +| `JumpCloudIcon` | `jumpcloud` | pair | #002B49 | #F7F7FB | 14.09 | 11.67 | jumpcloud.com/press (Ocean Blue / White Smoke); White Smoke is the brand's own reversed logo for dark backgrounds | +| `KafkaIcon` | `kafka` | pair | #000000 | #FFFFFF | 20.32 | 12.47 | apache/kafka | +| `KanidmIcon` | `kanidm` | fixed | #B1DEF4 | #B1DEF4 | 20.32 | 12.47 | artwork/logo-square.svg in github.com/kanidm/kanidm (full palette: #FF6600 #803300 #D45500 #2A3455 #B1B3B8 #CCCCCC) | +| `KeycloakIcon` | `keycloak` | fixed | #00B8E3 | #00B8E3 | 8.18 | 10.65 | keycloak.org's own mark, https://www.keycloak.org/resources/images/icon.svg (cyan #00B8E3/#33C6E9/#008AAA over greys #4D4D4D–#EDEDED, single theme) | +| `KlaviyoIcon` | `klaviyo` | pair | #1D1E20 | #FFFFFF | 16.14 | 12.47 | klaviyo.com --color-core-charcoal; the flag mark is the standalone logomark the site header collapses to, and the shape of klaviyo.com/icons/icon-512x512.png | +| `KoboToolboxIcon` | `kobotoolbox` | fixed | #2095F3 | #2095F3 | 3.05 | 3.95 | the kobotoolbox.org header logo and $kobo-blue in kobotoolbox/kpi jsapp/scss/colors.scss | +| `KustomerIcon` | `kustomer` | fixed | #FBEC2A | #FBEC2A | 14.08 | 12.47 | kustomer.com/images/kustomer/Kusty.svg | +| `LangfuseIcon` | `langfuse` | fixed | #FF5D5F | #FF5D5F | 2.91 | 4.47 | langfuse.com/brand "Icon - Color (SVG)", used unmodified | +| `LessIcon` | — | pair | #274F82 | #FFFFFF | 8.04 | 12.47 | github.com/less/logo (MIT) | +| `LineIcon` | `line` | fixed | #06C755 | #06C755 | 2.18 | 5.53 | LINE's official brand icon asset (line.me/en/logo) | +| `LinearIcon` | `linear` | pair | #222326 | #F4F5F8 | 15.20 | 11.44 | linear.app/brand | +| `LinkdingIcon` | — | pair | #5856E0 | #ADABF7 | 5.32 | 5.91 | sissbruecker/linkding | +| `LinkedinIcon` | `linkedin` | mixed | #0A66C2 | #0A66C2 | 5.50 | 12.47 | the official inbug SVGs embedded in brand.linkedin.com/in-logo | +| `LinodeIcon` | `linode` | fixed | #004B16 | #004B16 | 10.07 | 4.54 | Linode's own packages/manager/src/assets/logo/logo.svg in linode/manager @3e53c92, the last revision before the Akamai rebrand dropped it | +| `LumaAiIcon` | `lumaai` | pair | #000000 | #FFFFFF | 20.32 | 12.47 | lumalabs.ai (favicon-black.ico on light, favicon-white.ico on dark) | +| `MSSqlServerIcon` | — | fixed | #0094F0 | #0094F0 | 10.54 | 12.47 | learn.microsoft.com/en-us/azure/architecture/icons — Microsoft's anchor blue, a stop in its own SQL Server SVG and throughout the set's Fluent gradients | +| `MSTeamsIcon` | — | fixed | #A98AFF | #A98AFF | 12.96 | 12.47 | Microsoft's Teams-Icon-FY26 asset (cdn-dynmedia-1.microsoft.com, served on microsoft.com/microsoft-teams); every gradient stop here is verbatim from it | +| `MagentoIcon` | `magento` | fixed | #F26322 | #F26322 | 3.09 | 3.91 | Magento's own logo asset, magento2 lib/web/images/logo.svg | +| `MailchimpIcon` | `mailchimp` | fixed | #241C15 | #241C15 | 16.23 | 10.74 | mailchimp.com/about/brand-assets | +| `MailerLiteIcon` | `mailerlite` | fixed | #09C269 | #09C269 | 2.27 | 5.31 | mailerlite.com/brand-assets | +| `MailgunIcon` | `mailgun` | fixed | #F04126 | #F04126 | 3.70 | 12.47 | mailgun.com's own logo-mailgun-icon.svg | +| `MandrillIcon` | `mandrill` | pair | #241C15 | #FFFFFF | 16.23 | 12.47 | mailchimp.com/about/brand-assets and Mandrill's own mandrillapp.com/img/navigation/freddie.svg | +| `MapboxIcon` | `mapbox` | pair | #0E1012 | #FFFFFF | 18.45 | 12.47 | mapbox.com | +| `MarkdownIcon` | — | pair | #000000 | #FFFFFF | 20.32 | 12.47 | dcurtis/markdown-mark (public domain) | +| `MastodonIcon` | `mastodon` | mixed | #6364FF | #6364FF | 7.07 | 12.47 | https://joinmastodon.org/branding | +| `MatrixIcon` | `matrix` | pair | #000000 | #FFFFFF | 20.32 | 12.47 | matrix.org/branding | +| `MatteroomIcon` | `matteroom` | fixed | #134A81 | #134A81 | 8.74 | 12.47 | the MATTEROOM logomark vector at login.matteroom.com/images/login_logo.svg; square tile proportions taken from their own app icon at matteroom.com/favicon.ico | +| `MauticIcon` | `mautic` | pair | #4E5E9E | #FFFFFF | 5.94 | 12.47 | mautic.org/about/brand-logos-graphics (Mautic_Logo_LB.svg / Mautic_Logo_DB.svg); the "M" stays Sunglow #FDB933 in both, as the trademark policy requires the mark in its exact published form without alteration in colour | +| `McpIcon` | `mcp` | pair | #000000 | #FFFFFF | 20.32 | 12.47 | modelcontextprotocol/modelcontextprotocol | +| `MediumIcon` | `medium` | pair | #000000 | #FFFFFF | 20.32 | 12.47 | medium.design | +| `MeteosourceIcon` | `meteosource` | fixed | #FAD961 | #FAD961 | 2.87 | 9.01 | the logo in the meteosource.com site header (no brand page published) | +| `MezmoIcon` | `mezmo` | pair | #0A090C | #E6E6E5 | 19.22 | 9.99 | mezmo.com nav mark and docs.mezmo.com logo/light.png + logo/dark.png | +| `MicrosoftIcon` | `microsoft` | mixed | #F25022 | #F25022 | 3.88 | 7.24 | the official logo asset linked from Microsoft's logo third-party usage guidance | +| `MiroIcon` | `miro` | fixed | #FFDD33 | #FFDD33 | 16.46 | 9.29 | the Miro logo on miro.com | +| `MistralIcon` | `mistral` | inherits | #718096 | #A9B0BA | 3.88 | 5.71 | mistral.ai/favicon.svg (mid-band of the #FFAF01 -> #C4001D ramp); drawn here in currentColor, the monochrome variant mistral.ai/brand ships | +| `MixpanelIcon` | `mixpanel` | pair | #7856FF | #FFFFFF | 4.44 | 12.47 | brand.mixpanel.com/logo and /color (Purple 100); mixpanel.com ships the same pair as its light/dark favicons | +| `MollieIcon` | `mollie` | pair | #000000 | #FFFFFF | 20.32 | 12.47 | Mollie's app icon (my.mollie.com/assets/images/favicons/apple-touch-icon-180x180.png): a full-bleed disc with the lowercase m knocked out | +| `MondayIcon` | `monday` | fixed | #FB275D | #FB275D | 3.66 | 8.25 | monday.com's official logo pack (brand-monday.com/logo) | +| `MongodbIcon` | `mongodb` | pair | #00684A | #00ED64 | 6.60 | 7.90 | MongoDB brand resources and their LeafyGreen palette | +| `MotimateIcon` | `motimate` | fixed | #2DC89C | #2DC89C | 2.06 | 5.85 | motimateapp.com theme assets | +| `MqttIcon` | `mqtt` | pair | #660066 | #FFFFFF | 11.57 | 12.47 | mqtt/mqttorg-graphics | +| `Mysql` | `mysql` | inherits | #718096 | #A9B0BA | 3.88 | 5.71 | — | +| `NatsIcon` | `nats` | pair | #375C93 | #27AAE1 | 6.52 | 4.71 | cncf/artwork | +| `NeonDbIcon` | `neondb` | pair | #37C38F | #34D59A | 2.17 | 6.61 | neon.com/brand (neon-logomark-light-color.svg / neon-logomark-dark-color.svg) | +| `NetBoxIcon` | `netbox` | pair | #001423 | #FFFFFF | 18.07 | 12.47 | theme, so the second path carries its own fill- utilities | +| `NetlifyIcon` | `netlify` | pair | #05BDBA | #32E6E2 | 10.06 | 12.47 | netlify.com/brand (netlify-logo-monogram.zip, full-colour lightmode/darkmode) | +| `NetsuiteIcon` | `netsuite` | fixed | #BACCDB | #BACCDB | 7.71 | 7.57 | — | +| `NewsApiIcon` | `newsapi` | pair | #000000 | #FFFFFF | 20.32 | 12.47 | newsapi.org | +| `NextcloudIcon` | `ocs`, `nextcloud` | pair | #0082C9 | #FFFFFF | 4.03 | 12.47 | nextcloud.com | +| `NocoDbIcon` | `nocodb` | fixed | #4351E8 | #4351E8 | 11.12 | 3.30 | nocodb.com's own Logo.svg / favicon | +| `NotionIcon` | `notion` | fixed | #FFFFFF | #FFFFFF | 20.32 | 12.47 | Notion's own app icon (notion.com/front-static/logo-ios.png) | +| `NuIcon` | — | fixed | #4D9B05 | #4D9B05 | 3.38 | 3.57 | nushell/vscode-nushell-lang assets/nu.svg | +| `OdkIcon` | `odk` | fixed | #3E77B4 | #3E77B4 | 6.37 | 2.67 | ODK brand assets (getodk.org/legal/brand/) | +| `OktaIcon` | `okta` | pair | #000000 | #FFFFFF | 20.32 | 12.47 | okta.com | +| `OneSignalIcon` | `onesignal` | pair | #051B2C | #FFFFFF | 16.94 | 12.47 | OneSignal's official media kit (OneSignal-Logomark.svg / OneSignal-Logomark-White.svg), matching the prefers-color-scheme pair in their own onesignal.com/favicon.svg | +| `OpenRouterIcon` | `openrouter` | pair | #7624F4 | #C8FF00 | 6.10 | 10.55 | openrouter.ai/brand/v2/openrouter-glyph-{light,dark}.svg | +| `OpenWeatherIcon` | `openweather` | pair | #EA6D4A | — | 2.99 | — | openweather.co.uk/brand_guidelines | +| `OpenaiIcon` | `openai` | pair | #000000 | #FFFFFF | 20.32 | 12.47 | openai.com/brand (Blossom_Light.svg / Blossom_Dark.svg) | +| `OracleDBIcon` | `oracledb` | fixed | #C74634 | #C74634 | 4.67 | 2.59 | Oracle's own logo SVG at https://www.oracle.com/a/ocom/img/oracle-logo.svg | +| `OutreachIcon` | `outreach` | pair | #5951FF | #FFFFFF | 5.02 | 12.47 | outreach.ai | +| `PHPIcon` | — | fixed | #AEB2D5 | #AEB2D5 | 20.32 | 12.47 | php.net/images/logos/new-php-logo.svg (php.net/download-logos.php) | +| `PagerDutyIcon` | `pagerduty` | pair | #048A24 | #FFFFFF | 4.35 | 12.47 | pagerduty.com/brand "P icon" pack (P-GreenRGB.svg / P-WhiteRGB.svg) | +| `PandaDocIcon` | `pandadoc` | fixed | #248567 | #248567 | 4.39 | 12.47 | the PandaDoc logo shipped on pandadoc.com (header logo SVG and favicon); white monogram on the green tile in both themes | +| `PaychexIcon` | `paychex` | fixed | #004B8D | #004B8D | 8.50 | **1.42** | paychex.com's own logo SVG (themes/custom/paychex2/images/svg/logo-paychex.svg, .st0) | +| `PaylocityIcon` | `paylocity` | fixed | #ED2024 | #ED2024 | 4.20 | 12.47 | paylocity.com design-system CSS (.styleBGBrandGradient) | +| `PaypalIcon` | `paypal` | fixed | #002991 | #002991 | 11.77 | 6.93 | PayPal's own paypal-mark-color_new.svg (site header logo on paypal.com) | +| `PersonaIcon` | `persona` | fixed | #7379FD | #7379FD | 3.45 | 3.49 | https://withpersona.com/favicon.svg (Persona, identity verification) | +| `PersonioIcon` | `personio` | pair | #000000 | #FFFFFF | 20.32 | 12.47 | personio.design/brand/how-we-look/logo | +| `PhraseIcon` | `phrase` | pair | #181818 | #FFFFFF | 17.18 | 12.47 | Logo_primary.svg and Logo_black_background.svg on phrase.com/brand | +| `PineconeIcon` | `pinecone` | pair | #201D1E | #FFFFFF | 16.18 | 12.47 | pinecone.io/newsroom/media-kit | +| `PinterestIcon` | `pinterest` | fixed | #E60023 | #E60023 | 4.63 | 2.61 | Pinterest Gestalt tokens (color.icon.brand.primary = red.pushpin.450, identical in sema-color-light and sema-color-dark) | +| `PipedriveIcon` | `pipedrive` | fixed | #017737 | #017737 | 5.50 | 12.47 | pipedrive.com logo token --pd-puco-global-color-green-500 | +| `PlanetScaleIcon` | `planetscale` | pair | #1A1A1A | #FAFAFA | 16.84 | 11.95 | planetscale.com | +| `PocketIdIcon` | `pocketid` | pair | #000000 | #FFFFFF | 20.32 | 12.47 | pocket-id.org header logo (fill isDark ? #ffffff : #000000) and pocket-id/pocket-id frontend/src/lib/components/logo.svelte | +| `PostgresIcon` | `postgresql` | mixed | #336791 | #336791 | 20.32 | 12.47 | the official 3-colour Slonik SVG on wiki.postgresql.org/wiki/Logo | +| `PostmarkIcon` | `postmark` | fixed | #FFDE00 | #FFDE00 | 20.32 | 9.33 | postmarkapp.com/images/logo-stamp-simple.svg | +| `PowershellIcon` | — | fixed | #00FF18 | #00FF18 | 20.32 | 12.47 | github.com/PowerShell/PowerShell/blob/master/assets/ps_black_64.svg | +| `PusherIcon` | `pusher` | pair | #300D4F | #FFFFFF | 15.68 | 12.47 | pusher.com media kit (Pusher logo primary.png / Pusher logo secondary.png) | +| `PushoverIcon` | `pushover` | fixed | #249DF1 | #249DF1 | 2.83 | 12.47 | support.pushover.net/i63-pushover-logos-and-usage | +| `QoveryIcon` | `qovery` | fixed | #642DFF | #642DFF | 6.05 | 2.00 | qovery.com/logos/qovery-logo-black.svg | +| `QuickbooksIcon` | `quickbooks` | fixed | #2CA01C | #2CA01C | 3.30 | 3.65 | the QuickBooks logo SVG on intuit.com's press room | +| `RIcon` | — | fixed | #276DC3 | #276DC3 | 6.46 | 7.89 | r-project.org/logo (gradient stops copied from the authoritative Rlogo.svg) | +| `RaindropIcon` | `raindrop` | fixed | #1988E0 | #1988E0 | 5.33 | 12.47 | app.raindrop.io/assets/icon_raw.svg and raindrop.io icon_128.png | +| `ReactIcon` | — | pair | #087EA4 | #58C4DC | 4.48 | 6.14 | react.dev brand menu (images/brand/logo_light.svg, logo_dark.svg) | +| `ReadmeIcon` | `readme` | pair | #213AFF | #FFFFFF | 6.53 | 12.47 | readme.com's own prefers-color-scheme favicon pair (favicon-213aff.ico / favicon-ffffff.ico) | +| `ReadwiseIcon` | `readwise` | pair | #000000 | #FFFFFF | 20.32 | 12.47 | readwise.io's logo-standalone-dark.svg (light) and logo-standalone-white.svg (dark) | +| `RecraftIcon` | `recraft` | fixed | #000000 | #000000 | 20.32 | 12.47 | Recraft's press-kit "Icon White" mark (https://www.recraft.ai/press-releases) | +| `RedditIcon` | `reddit` | fixed | #FF6600 | #FF6600 | 20.32 | 12.47 | redditinc.com/brand ("a stylized Snoo head contained within an OrangeRed (#FF4500) conversation bubble") | +| `RenderIcon` | `render` | pair | #000000 | #FFFFFF | 20.32 | 12.47 | render.com | +| `ReplicateIcon` | `replicate` | pair | #000000 | #FFFFFF | 20.32 | 12.47 | replicate.com header logo (glyph is currentColor; site CSS sets #000, and #FFF under .dark) | +| `ResendIcon` | `resend` | pair | #000000 | #FDFDFD | 20.32 | 12.26 | cdn.resend.com/brand/resend-icon-black.svg and resend-icon-white.svg | +| `RingCentralIcon` | `ringcentral` | pair | #FF7A00 | #FFFFFF | 2.53 | 12.47 | assets.ringcentral.com/us/brand-library/logos/ringcentral-logo.zip (RingCentral logo fullcolor.svg / RingCentral logo white.svg) | +| `RocketChatIcon` | `rocketchat` | fixed | #F5455C | #F5455C | 3.45 | 3.50 | Rocket.Chat brand colours (docs.rocket.chat/v1/docs/colors), the primary red of their logo | +| `RssIcon` | `rss` | fixed | #FFA500 | #FFA500 | 1.91 | 12.47 | Mozilla's feed icon guidelines (mozilla.org/en-US/foundation/feed-icon-guidelines/), which fix no exact hex | +| `RubyIcon` | — | fixed | #FB7655 | #FB7655 | 10.63 | 12.47 | the official logo kit at ruby-lang.org/en/about/logo | +| `RunPodIcon` | `runpod` | pair | #5D29F0 | #FFFFFF | 6.68 | 12.47 | runpod.io/brandkit | +| `RustIcon` | — | pair | #000000 | #FFFFFF | 20.32 | 12.47 | rust-lang/rust-artwork | +| `S3Icon` | `s3` | fixed | #7AA116 | #7AA116 | 2.93 | 12.47 | AWS Architecture Icons (Icon-package_07312026, Arch_Storage/64/Arch_Amazon-Simple-Storage-Service_64.svg) | +| `SageIcon` | `sage_intacct` | pair | #000000 | #00D639 | 20.32 | 6.34 | @sage/design-tokens --logo-sage-bg-default | +| `SalesflareIcon` | `salesflare` | fixed | #0053FF | #0053FF | 5.52 | 2.19 | salesflare.com's own `--color--major-blue` design token | +| `SalesforceIcon` | `salesforce` | fixed | #00B3FF | #00B3FF | 2.29 | 5.28 | brand.salesforce.com/brand/color | +| `SassIcon` | — | fixed | #CC6699 | #CC6699 | 3.43 | 12.47 | sass-lang.com's own style guide token --sl-color--hopbush (assets/dist/css/sass.css) | +| `SegmentIcon` | `segment` | fixed | #52BD94 | #52BD94 | 2.24 | 5.38 | Segment's own app favicon (app.segment.com) and Evergreen green500 #52BD95 | +| `SendflakeIcon` | `snowflake` | pair | #29B5E8 | — | 2.29 | — | snowflake.com/brand-guidelines | +| `SendgridIcon` | `sendgrid` | fixed | #00B3E3 | #00B3E3 | 3.81 | 8.57 | styleguide.sendgrid.com/colors.html | +| `SensorTowerIcon` | `sensortower` | fixed | #00CFB8 | #00CFB8 | 1.91 | 12.47 | sensortower.com/favicon.svg, copied verbatim | +| `SentryIcon` | `sentry` | pair | #181225 | #FFFFFF | 17.64 | 12.47 | sentry.io/branding logo generator (Dark/Light themes, "Invert in dark mode") | +| `ServiceNowIcon` | `servicenow` | fixed | #62D84E | #62D84E | 1.77 | 6.80 | servicenow.com/company/servicenow-logo.html (servicenow-logo-icon.svg) | +| `ShopifyIcon` | `shopify` | fixed | #95BF47 | #95BF47 | 3.75 | 12.47 | shopify.com/brand-assets (shopify-logo-shopping-bag-full-color.svg: #95BF47/#5E8E3E/#fff) | +| `ShortcutIcon` | `shortcut` | pair | #494BCB | #797ADE | 6.45 | 3.36 | shortcut.com/branding (mark-default.svg; the reversed lockup uses #797ADE on dark) | +| `ShutterstockIcon` | `shutterstock` | pair | #000000 | #FFFFFF | 20.32 | 12.47 | brand.shutterstock.com | +| `SigNozIcon` | `signoz` | fixed | #FF5E19 | #FF5E19 | 3.63 | 12.47 | signoz.io/img/SigNozLogo-orange.svg | +| `Slack` | `slack` | mixed | #E01E5A | #E01E5A | 4.51 | 6.52 | slack.com's own nav logo (a.slack-edge.com/38f0e7c/marketing/img/nav/logo.svg, linked from slack.com/media-kit) | +| `SmartsheetIcon` | `smartsheet` | pair | #031C59 | #FFFFFF | 15.46 | 12.47 | brandguides.brandfolder.com/smartsheet-visual-guide/basics | +| `SnowflakeIcon` | — | pair | #29B5E8 | — | 2.29 | — | snowflake.com/brand-guidelines | +| `SpeechifyIcon` | `speechify` | pair | #2F43FA | #FFFFFF | 6.15 | 12.47 | the Speechify brand kit (speechify.com/brand-kit, Logomark_blue.svg and Logomark_white.svg) | +| `SplitwiseIcon` | `splitwise` | pair | #1CC29F | — | 2.19 | — | splitwise.com/press (sw.svg / sw-wide.svg / bg-primary.svg) | +| `SpotifyIcon` | `spotify` | pair | #1ED760 | #FFFFFF | 1.86 | 12.47 | developer.spotify.com/documentation/design (2024 Primary Logo icon pack) | +| `SquareIcon` | `square` | pair | #000000 | #FFFFFF | 20.32 | 12.47 | Square_Logo_2025 in squareup.com/us/en/press/logo | +| `StraleIcon` | `strale` | pair | #0D0D0E | #F2F2F3 | 18.80 | 11.15 | strale.dev favicon.svg and the site's own --foreground token | +| `StravaIcon` | `strava` | fixed | #FC5200 | #FC5200 | 3.20 | 3.77 | developers.strava.com/guidelines (Strava API logo pack, orange SVGs) | +| `StripeIcon` | `stripe` | fixed | #533AFD | #533AFD | 5.99 | 12.47 | Stripe's own favicon.svg and Stripe_logo_kit.zip (stripe.com/newsroom/brand-assets) | +| `SupabaseIcon` | `supabase` | fixed | #3ECF8E | #3ECF8E | 3.75 | 6.25 | supabase.com/brand-assets | +| `SurrealdbIcon` | `surrealdb` | mixed | #D255FE | #D255FE | 7.33 | 5.71 | surrealdb.com/brand | +| `SvelteIcon` | — | fixed | #FF3E00 | #FF3E00 | 3.42 | 12.47 | sveltejs/branding (svelte-logo.svg, white cutout #fff) | +| `TallyIcon` | `tally` | pair | #000000 | #FFFFFF | 20.32 | 12.47 | the "Tally Icon - Black" / "Tally Icon - White" files in the icon pack on tally.so/help/press-kit, matching the live tally.so/favicon.svg | +| `TaskadeIcon` | `taskade` | pair | #000000 | #FFFFFF | 20.32 | 12.47 | taskade.com/press (Mascot Mark light = agent_taskade.svg, Genesis Icon dark = taskade-icon-dark.svg) | +| `TelegramIcon` | `telegram` | fixed | #2AABEE | #2AABEE | 2.92 | 12.47 | Telegram's press-kit Logo.svg (telegram.org/press) | +| `TelnyxIcon` | `telnyx` | pair | #000000 | #00E3AA | 20.32 | 12.47 | telnyx.com | +| `TerraIcon` | `terra` | fixed | #008AFF | #008AFF | 20.32 | 10.43 | tryterra.co/providers/terra_icon.svg, the only vector square mark Terra ships (the site logo is a "TERRA API" wordmark, the favicon a raster .ico) | +| `TheirStackIcon` | `their_stack` | pair | #000000 | #FFFFFF | 20.32 | 12.47 | theirstack.com/en/docs/brand, which lists both as core brand colours | +| `ThreadsIcon` | `threads` | pair | #000000 | #FFFFFF | 20.32 | 12.47 | Meta's Threads Brand Resource Center logo pack (meta.com/brand/resources/threads) | +| `TodoistIcon` | `todoist` | fixed | #E44232 | #E44232 | 3.97 | 3.04 | Todoist Brand Guidelines (doist.com/brand-assets/todoist-logo.zip), "Red — the primary brand color for Todoist" | +| `TogetherAiIcon` | `togetherai` | fixed | #EF2CC1 | #EF2CC1 | 3.50 | 6.46 | together.ai's brand page (https://www.together.ai/brand) | +| `TogglIcon` | `toggl` | pair | #2C1138 | #E57CD8 | 16.33 | 4.87 | Toggl Track media toolkit (toggl.com/track/media-toolkit, icon-dark-purple.svg / icon-pink.svg) | +| `TomorrowIoIcon` | `tomorrow` | fixed | #004CF8 | #004CF8 | 6.00 | 12.47 | tomorrow.io's own design tokens (--color-logo-blue in site-frame.min.css, matching the header lockup SVG and logo-490.png) | +| `TrelloIcon` | `trello` | fixed | #1558BC | #1558BC | 6.44 | 12.47 | Atlassian Design logo library (atlassian.design/foundations/logos → trello_app.zip, Trello_icon.svg) | +| `TripadvisorIcon` | `tripadvisor` | fixed | #002B11 | #002B11 | 15.01 | **1.24** | 2025 Tripadvisor Brand Guidelines for Partners, tripadvisor.mediaroom.com | +| `TursoIcon` | `turso` | pair | #183134 | #FFFFFF | 13.30 | 12.47 | turso.tech/brand (Dark Teal and white logomark variants) | +| `TwilioIcon` | `twilio` | fixed | #F22F46 | #F22F46 | 3.86 | 3.13 | twilio.com (mask-icon color, favicon and apple-touch-icon artwork) | +| `TwitchIcon` | `twitch` | fixed | #9146FF | #9146FF | 4.49 | 2.69 | brand.twitch.com | +| `TwitterIcon` | `twitter` | pair | #000000 | #FFFFFF | 20.32 | 12.47 | about.x.com | +| `TypeformIcon` | `typeform` | pair | #000000 | #FFFFFF | 20.32 | 12.47 | typeform.com/brand | +| `UltravoxIcon` | `ultravox` | fixed | #BB3B57 | #BB3B57 | 6.67 | 7.65 | the ultravox.ai favicon (framerusercontent.com/images/hzAEdihxJ11mv3l4trNh2WprE.svg) | +| `VectaraIcon` | `vectara` | fixed | #7E00FF | #7E00FF | 7.00 | 9.80 | — | +| `VercelIcon` | `vercel` | pair | #000000 | #FFFFFF | 20.32 | 12.47 | vercel.com | +| `VismaIcon` | `visma` | pair | #131313 | #FFFFFF | 17.98 | 12.47 | design.visma.com/logo (VA symbol from the official Visma Logopack) | +| `VueIcon` | — | fixed | #42B883 | #42B883 | 8.97 | 5.00 | vuejs/art logo.svg | +| `WebflowIcon` | `webflow` | fixed | #146EF5 | #146EF5 | 4.44 | 2.72 | brand.webflow.com/brand-assets | +| `WhatsappBusinessIcon` | `whatsapp_business` | fixed | #25D366 | #25D366 | 1.92 | 6.29 | WhatsApp's Digital_Glyph_Green_RGB_2026.svg, shipped by whatsapp.com/business (→ whatsappbusiness.com) | +| `WizIcon` | `wiz` | pair | #0254EC | #FFFFFF | 5.84 | 12.47 | wiz.io/press media kit logo pack (WizLogo_Blue_Vector.svg / WizLogo_White_Vector.svg) | +| `WooCommerceIcon` | `woocommerce` | pair | #873EFF | #FFFFFF | 4.88 | 12.47 | the Woo logo pack at woocommerce.com/brand-and-logo-guidelines (Woo_logo_color.svg and Woo_logo_white.svg) | +| `WordpressIcon` | `wordpress` | pair | #32373C | #FFFFFF | 11.63 | 12.47 | wordpress.org/about/logos/ | +| `XataIcon` | `xata` | fixed | #8468F6 | #8468F6 | 3.84 | 3.14 | xata.io/brand (logo-symbol.svg) | +| `XeroIcon` | `xero` | fixed | #13B5EA | #13B5EA | 2.30 | 12.47 | xero.com favicon.svg and the site header logo (Xero__LogoPath fill) | +| `YamlIcon` | — | mixed | #CB171E | #CB171E | 5.52 | 5.71 | yaml.org's own assets/favicon.svg and assets/logo.png; the Y, M and L carry no fill in YAML's SVG, so they take the surrounding text colour | +| `YelpIcon` | `yelp` | fixed | #FF1A1A | #FF1A1A | 3.75 | 3.22 | yelp.com/brand (burst_red.svg and the official logo kit) | +| `YnabIcon` | `ynab` | pair | #3B5EDA | #FEF9E6 | 5.35 | 11.82 | ynab.com press kit tree logo (Tree Logo Blurple.svg / Tree Logo Buttermilk.svg — the buttermilk reverse is what ynab.com itself uses on its dark footer) | +| `YoutubeIcon` | `youtube` | fixed | #FF0033 | #FF0033 | 3.83 | 12.47 | brand.youtube/color (YouTube Red, updated from #FF0000) | +| `ZammadIcon` | `zammad` | fixed | #CD2015 | #CD2015 | 7.62 | 9.73 | zammad.com favicon-32x32.svg | +| `ZendeskIcon` | `zendesk` | pair | #11110D | #FFFFFF | 18.31 | 12.47 | zendesk.com | +| `ZeroTierIcon` | `zerotier` | fixed | #FFB25B | #FFB25B | 16.58 | 6.99 | zerotier.com's own icon.svg and logo lockups | +| `ZitadelIcon` | `zitadel` | pair | #232323 | #FFFFFF | 15.21 | 12.47 | zitadel/zitadel console assets zitadel-logo-solo-dark.svg / zitadel-logo-solo-light.svg | +| `ZixflowIcon` | `zixflow` | pair | #141414 | #FFFFFF | 17.83 | 12.47 | docs.zixflow.com logo pack (logo/light.svg / logo/dark.svg) | +| `ZohoIcon` | `zoho` | pair | #000000 | #FFFFFF | 20.32 | 12.47 | zoho.com/branding (zoho-logo-web.svg / zoho-logo-white.svg) | +| `ZoomIcon` | `zoom` | pair | #0B5CFF | #FFFFFF | 5.09 | 12.47 | brand.zoom.com | +| `ZuploIcon` | `zuplo` | fixed | #FF00BD | #FF00BD | 3.39 | 3.56 | https://zuplo.com/brand | + +## Rules the brand imposes + +Constraints that would otherwise be broken by a well-meaning change. + +- **AblyIcon** — "Don't use other colours or gradients for the symbol." +- **AcceloIcon** — The mark keeps these three fills in both themes; only the wordmark (not drawn here) swaps #10202D for white. +- **AmqpIcon** — No brand colour: AMQP is an OASIS protocol, not a vendor, and amqp.org publishes no palette (https://www.amqp.org/legal.html). Generic glyph, deliberately monochrome — keep it on currentColor. +- **AnsibleIcon** — The mark is a solid disc knocked out with a white "A", so the pair is applied by inverting rather than currentColor: recolouring the disc alone would leave white on light grey. +- **ApifyIcon** — White/black variants are reserved for monochromatic contexts, so the tricolour mark stays in both themes. +- **AppwriteIcon** — Brand asks that the logo not be altered, so no per-theme variant. +- **ArcGisIcon** — Esri publishes no reversed variant for this badge and forbids altering its logos. +- **AsanaIcon** — Asana's guidelines forbid recolouring: the symbol always appears in coral, on light and dark backgrounds alike. +- **AssemblyAiIcon** — Two colours per theme, so the second stroke carries its own fill- utilities: #777673 on light, #FFFFFF on dark. +- **AttioIcon** — The mark is a filled compound path; stroking it instead thickens it and leaks the default black fill. +- **Auth0Icon** — Okta's content terms forbid altering the mark, so ship only these published variants. +- **AutheliaIcon** — authelia.com/reference/guides/branding permits format/layout changes only — do not alter the design. +- **AuthentikIcon** — The white variant is the brand's own asset for dark backgrounds; proportions and colour must not be altered otherwise. +- **AwsEcrIcon** — AWS ships one flat fill for both themes; the gradient tile was retired in the 2023 accessibility refresh. +- **AwsIcon** — aws.amazon.com/trademark-guidelines forbids altering the logo's colour, so only these two published variants may be used. +- **BaserowIcon** — The mark keeps these three colours on light and dark; only the wordmark reverses to white. +- **BeamerIcon** — The isotype is monochrome in all first-party artwork. +- **BigQueryIcon** — Google publishes no reversed variant, so the same mark is used on both themes. +- **BitbucketIcon** — Atlassian ships brand/neutral/inverse only: "don't use unapproved color combinations". +- **BitlyIcon** — Bitly's reversed logomark is white over orange, not over neutral dark, so the orange mark is used on both. +- **BloggerIcon** — Google publishes no reversed variant. +- **BlueskyIcon** — The downloadable media-kit butterfly still ships the older #006AFF, but the palette is the normative source: "use only the official color values above. Do not substitute, tint, or approximate." White is the approved monochrome variant for dark backgrounds. +- **BoxIcon** — box.com/legal/trademark forbids any other recolouring of the mark. +- **BrevoIcon** — Brevo publishes no per-theme variant of the app mark; the reversed "Mint" #F9FFF6 asset is the wordmark only. +- **BrowserlessIcon** — Its own prefers-color-scheme block sets black on light, white on dark. +- **BubbleIcon** — Bubble's brand terms forbid re-colouring the mark beyond its published dark/light pair. +- **BuildkiteIcon** — Buildkite ships a single mark "for any context", so there is no per-theme variant, and asks that it not be altered. +- **ButtondownIcon** — Brand forbids recolouring the logo, so no per-theme variant. +- **CSharpIcon** — Its README forbids altering the mark, so the same full-colour icon is used on light and dark. +- **CalcomIcon** — Cal.com's design system states it is deliberately a grayscale brand and publishes exactly two logo variants. +- **CalendlyIcon** — Guidelines: "Only show our logo and lockups in blue or white." +- **CertopusIcon** — Verbatim copy of the brand's own circle mark: #2C353D and the white disc are its other fixed tones, not a dark-theme variant. +- **CircleCiIcon** — Guidelines require Terminal (#161616) on light backgrounds and White on dark, and forbid any color not named in them. +- **CiscoIcon** — Cisco requires all parts of the mark be knocked out to white on dark backgrounds. +- **ClerkIcon** — Same two-tone symbol on light and dark; the mono symbol-dark/symbol-light pair is Clerk's alternate for single-colour contexts. +- **ClickhouseIcon** — Brand forbids recolouring the mark, so only its own published pair is used. +- **CloseIcon** — Close forbids modifying the logo, so the same colours are kept on light and dark. +- **CloudflareIcon** — Cloudflare's logo guidelines forbid altering the colours or filling the flare, so the flare stays knocked out on both themes. +- **CloudinaryIcon** — Cloudinary Blue is reserved for the logo; no other recolouring is permitted. +- **CockroachDbIcon** — The full-colour mark is a cyan-to-purple gradient; Cockroach Labs reduces it to solid white on dark backgrounds. +- **CoinbaseIcon** — Coinbase asks that the mark not be altered or recoloured, so only these two published variants are used. +- **ComapeoIcon** — Awana Digital publishes no reversed variant, so dark mode uses the brand's own accent blue #0066FF from CoMapeoLogo.svg (digidem/comapeo-mobile); the navy is 1.3:1 on dark surfaces. +- **ConfluenceIcon** — Atlassian requires the logo be used without modification, and its brand appearance is identical in light and dark. +- **ContentfulIcon** — Same full-colour mark on light and dark. +- **ContiguityIcon** — The `>_` glyph is knocked out to the opposite colour, so it carries its own fill- utilities. +- **ConvertKitIcon** — ConvertKit rebranded to Kit in 2024. +- **CssIcon** — Small-size variant; the only per-theme variants published are mono black/white fallbacks, so the rebeccapurple tile is kept in both themes. +- **DatadogIcon** — Datadog publishes one mark per background, the purple tile with Bits knocked out on light and the white Bits silhouette on dark, and forbids recolouring or inverting either. +- **DatoCmsIcon** — Brand kit forbids altering the logo's shape or colour. +- **DbtIcon** — Their Trademark Policy states "The dbt logo mark color cannot be altered", so this stays orange on both themes. +- **DeelIcon** — Post-rebrand the period is a square in the wordmark colour, not a blue circle. +- **DeepInfraIcon** — Their brand guidelines say "use the primary white logo on dark backgrounds", where the connector bars invert to #FFFFFF. +- **DenoIcon** — Deno publishes no hex and forbids colorizing; the black "Light (no outline)" and white "Dark (outlined)" marks are separate artworks to be swapped per background, never inverted. +- **DiscordIcon** — Discord forbids recolouring the logo, so no per-theme variant. +- **DiscourseIcon** — Only the outer bubble reverses; the five inner colours are the same in both variants. +- **DockerIcon** — Docker requires its logos appear only in its primary brand colours. +- **DropboxIcon** — Dropbox's branding terms forbid recolouring the logo, and their inverse-theme token keeps the same blue. +- **DuckDbIcon** — The pair is a full inversion, so the duck carries its own fill- utilities; the manual forbids recolouring outside these two brand hexes. +- **DustIcon** — Dust's guidelines forbid recolouring the logo. +- **DynatraceIcon** — Guidelines forbid colorizing the logo, so all six fills stay fixed in both themes. +- **EdgeDbIcon** — EdgeDB is now Gel — edgedb.com redirects to geldata.com — so this is Gel's "g" symbol, not the retired EDGE|DB wordmark. +- **EnodeIcon** — Their own favicon carries the pair in a prefers-color-scheme block. +- **ExaIcon** — Blue for standard applications, white on dark backgrounds. +- **FigmaIcon** — Figma's guidelines forbid modifying the marks, so the five-colour original is used on both themes. +- **FirebaseIcon** — Same full-colour artwork on light and dark; the guidelines forbid recolouring or redrawing the mark. +- **FoxentryIcon** — Fixed tri-tone mark, no per-theme variant: the brand ships a separate greyscale logo rather than a recoloured one. +- **FreshdeskIcon** — Freshworks publishes no reversed variant: the white glyph always sits on the green leaf. +- **FunkwhaleIcon** — Identity guidelines forbid recolouring. +- **GSheetsIcon** — Google forbids recolouring its marks, so the same full-colour artwork is used on both themes. +- **GcalIcon** — Google forbids modifying its logos, colour included, so this stays fixed with no per-theme pair. +- **GdriveIcon** — Google forbids modifying its logos "in any way, including changing the color", so this stays full-colour with no per-theme pair. +- **GiphyIcon** — Same full-colour mark on light and dark. +- **GitBookIcon** — #1C1917 is the marketing palette's dark base, not the logomark. +- **GitIcon** — A white reversed logomark exists, but git-scm.com's own dark theme exempts the mark from inversion and keeps it orange. +- **GithubIcon** — GitHub allows the Invertocat in white or black only and forbids recolouring it, so the pair is fixed here rather than inherited from the caller. +- **GmailIcon** — Google's brand guidelines forbid recolouring the mark. +- **GoogleAiIcon** — Google ships no reversed variant; the same gradient is used on light and dark. +- **GoogleCalendarIcon** — Google's trademark guidelines forbid distorting or altering a brand feature, so no per-theme recolour. +- **GoogleCloudIcon** — Google forbids recolouring its logos. +- **GoogleDriveIcon** — Google's Drive branding guide permits resizing only — no other change to the logo — so no per-theme recolour. +- **GoogleFormsIcon** — Google's brand guidelines forbid modifying or recolouring its product icons. +- **GoogleIcon** — developers.google.com/identity/branding-guidelines forbids changing the colour of the G. +- **GorgiasIcon** — The guide allows only black or white for the symbol: "Do not use gray!". +- **GristIcon** — "Keep it exactly as depicted — no recoloring, no cropping." +- **GroqIcon** — Logo use in a UI requires a license from Groq. +- **HoldedIcon** — Holded ships one flat red mark for both themes; the red-orange gradient is retired. +- **HubspotIcon** — The legacy Coral #FF7A59 is not the current logo color. +- **IfsIcon** — IFS's negative lockup reverses only the wordmark, so the symbol keeps its #8427E2-to-#72C9F8 gradient on dark. +- **IftttIcon** — IFTTT's brand guidelines state "Our wordmark may be used in solid white or black" and publish no other hex for the mark. +- **IntercomIcon** — Intercom ships the mark as fill="currentColor" bound to its nav foreground token, so it takes the colour of the surface it sits on. +- **JavaIcon** — The Coffee Cup mark is licensee-only and "you may not use a modified version of the Coffee Cup logo" — do not recolour it or flatten it to currentColor. +- **JavaScriptIcon** — Fixed mark: yellow field, black lettering, no per-theme variant. +- **JiraIcon** — The logomark is identical on light and dark; only the wordmark changes colour. +- **JoomlaIcon** — Joomla's trademark policy forbids recolouring the mark, so there is no per-theme variant. +- **JotformIcon** — The other three bars keep their fixed brand colours in both themes. +- **JsonIcon** — JSON itself has no brand owner or published colours — json.org states none — so this is a Material palette pick, not a brand colour. +- **KanidmIcon** — Kanidm's artwork is CC-BY-NC-ND — no recolouring or other derivatives. +- **KlaviyoIcon** — Klaviyo draws it in currentColor, hence the white swap on dark. +- **LangfuseIcon** — Langfuse's trademark terms forbid modifying the assets. +- **LineIcon** — LINE forbids any change to the logo's colour, so there is no reversed variant. +- **LinearIcon** — Guidelines ship a light/dark logomark pair and forbid altering the assets in any other way. +- **LinkedinIcon** — That page forbids recolouring: only the approved blue, black and white variants. +- **LinodeIcon** — The keyline path stays unfilled so it follows currentColor instead of the source's near-black #231f20. +- **LumaAiIcon** — The two faces ship at 65% opacity, which is what makes their overlap read as a cube. +- **MSSqlServerIcon** — Microsoft licenses its product icons for diagrams, docs, and training only, and forbids cropping, rotating, or reshaping them. +- **MSTeamsIcon** — Microsoft's trademark guidelines forbid altering their brand assets, so the full-colour mark ships unchanged in both themes. +- **MailchimpIcon** — Mailchimp forbids altering the files, so both official tones are painted and neither is recoloured per theme. +- **MailerLiteIcon** — Their IP guidelines forbid altering or recolouring the mark. +- **MailgunIcon** — Mailgun ships no reversed variant; the tile is identical on light and dark. +- **MandrillIcon** — On dark their rule is the reversed (white) Freddie; Cavendish Yellow #FFE01B is a background colour, never the mark. +- **MarkdownIcon** — Spec: keep the enclosure's aspect ratio and radius, keep the M/arrow/box relative sizes, and draw all three in one colour. +- **MastodonIcon** — Swap to the black or white logo rather than recolouring when contrast fails. +- **MatrixIcon** — Artwork is the Foundation's matrix-icon.svg verbatim; the trademark policy forbids altering it. +- **MediumIcon** — Guidelines mandate black or white only for both the wordmark and the icon and forbid "any other colors, gradients, or filled with images". +- **MezmoIcon** — The star stays #F4B811 in both themes. +- **MicrosoftIcon** — Microsoft forbids recolouring the symbol, so it stays full-colour on both themes. +- **MistralIcon** — Brand forbids any other recolouring. +- **MixpanelIcon** — "The Mixpanel logo is only ever used in three colors: black, white and the primary brand purple." +- **MollieIcon** — The m is the glyph from Mollie-Logo-Black-2023.svg (Mollie logo pack, mollie.com/resources), scaled and placed to match that icon pixel for pixel; the logo pack itself ships only the 320x94 wordmark. Mollie publishes black and white variants, so the pair flips for dark mode. +- **MondayIcon** — All three colours are required; the brand forbids monochrome or recoloured versions, so no dark-theme variant. +- **MongodbIcon** — MongoDB permits only four logo colours, chosen for contrast with the background, and forbids any other recolour. +- **MotimateIcon** — Motimate is a registered trademark of Motimate AS (Kahoot!). +- **Mysql** — Used under Fair Use: https://fr.wikipedia.org/wiki/Fichier:MySQL.svg +- **NeonDbIcon** — Neon forbids recolouring, so only these published variants may be used. +- **NetlifyIcon** — Two colours per theme, so the "n" carries its own fill- utilities: #014847 on light, #FFFFFF on dark. +- **NetsuiteIcon** — Pre-Oracle NetSuite "N" mark. #125580/#baccdb approximate netsuite.com's own 2014 logo art, which is itself inconsistent: /portal/common/img/ns-logo.png is #14487e/#b9c9d5 and /portal/common/img/logo-ns-mobile.png is #13527d/#b6c7d5 (both via web.archive.org/web/2014/). Not Oracle's current NetSuite mark, which is a different logo in a different palette (#264759/#36677D/#94BFCE/#E2C06B). +- **NocoDbIcon** — NocoDB publishes no reversed variant; the full-colour mark is used on light and dark alike. +- **NotionIcon** — The plate is fixed, not theme-swapped: the mark is pure black and disappears on dark backgrounds without it. +- **NuIcon** — Nushell registers that one file as both the `light` and `dark` icon, so the green is not theme-swapped. +- **OdkIcon** — ODK publishes no reversed or monochrome variant. +- **OktaIcon** — Okta's official April-2025 logo package (logos-04-2025.zip) ships the mark in Black and White only. +- **OpenWeatherIcon** — Their negative (dark-background) logo reverses only the wordmark; the symbol stays brand orange. +- **OpenaiIcon** — The guidelines state "DON'T add any colors to the Blossom" — black or white only. +- **OracleDBIcon** — Oracle reserves its logo for licensees. +- **PHPIcon** — Official logo, CC BY-SA 4.0: keep it verbatim and credit Colin Viebrock rather than recolouring. +- **PaychexIcon** — The isolated P is the square mark Paychex ships as its own 192x192 app icon. Paychex requires prior approval for any use of its marks. +- **PaypalIcon** — The third fill is the deep/bright blue overlap: a two-colour or flat fill loses it. +- **PersonioIcon** — Black on light, white on dark or coloured backgrounds. +- **PhraseIcon** — The green wedge stays #03EAB3 in both — Phrase forbids altering the logo mark colour. +- **PineconeIcon** — The mark is stroke-only, so fill must stay none. +- **PinterestIcon** — Brand guidelines: "Do not alter the logo colour." +- **PipedriveIcon** — Pipedrive's partner media kit says "do not alter, rotate, modify or animate the logo", so the mark keeps its published colours on both themes. +- **PostgresIcon** — The PostgreSQL trademark policy forbids recolouring the mark without prior approval. +- **PostmarkIcon** — Postmark publishes no reversed variant. +- **PowershellIcon** — Trademarked Microsoft logo, exempt from that repo's MIT license. The #00FF18 line below is opacity-0 in the upstream asset and paints nothing. +- **PusherIcon** — Only the colourways shown in their brand guidelines are permitted. +- **PushoverIcon** — Forbids recolouring. +- **QoveryIcon** — The mark keeps the same purple in Qovery's white lockup for dark backgrounds, so there is no reversed variant. +- **QuickbooksIcon** — Intuit forbids altering the mark. +- **RIcon** — R Foundation licenses the mark CC-BY-SA 4.0 / GPL-2 — attribution required, changes must be indicated. +- **RaindropIcon** — Full-colour mark, no reversed variant published. +- **ReadwiseIcon** — The serif R and its highlight block are knocked out to the opposite colour, so they carry their own fill- utilities. Do not restore the mix-blend-mode: multiply wrapper Readwise's dark file carries: it turns the knocked-out white to the backdrop colour on anything but a white page. +- **RecraftIcon** — The plated mark carries its own background: recraft.ai serves it to prefers-color-scheme light and dark alike — not a theme pair. +- **RedditIcon** — Reddit publishes no reversed variant; the icon must always appear in Orangered when in colour. +- **RenderIcon** — Official Render Brand Kit contains only Black and White logomark folders and the SVGs use pure black / pure white. +- **ResendIcon** — Brand guidelines forbid multi-color use or altering the mark, so these are the only two published fills. +- **RssIcon** — Never rotate or flip the mark. +- **RubyIcon** — CC BY-SA 2.5; the kit's LICENSE asks that the mark not represent anything other than the Ruby language. +- **RunPodIcon** — Brand forbids recolouring, so both values are its own published cube-icon variants. +- **RustIcon** — rust-lang.org ships only rust-logo-blk.svg (pure black). +- **S3Icon** — AWS ships no dark variant for service icons. +- **SageIcon** — Sage sets its logo black on light surfaces and Sage green only on dark ones. +- **SalesforceIcon** — Salesforce reserves the white/reversed cloud for its own blue backgrounds, so the blue mark stands in both themes. +- **SegmentIcon** — Segment ships the mark in one flat green and publishes no reversed variant. +- **SendflakeIcon** — Snowflake Blue is the only approved logo color; the sole alternate is a white reverse reserved for full-bleed Snowflake Blue. +- **SendgridIcon** — Twilio's trademark guidelines forbid recolouring the mark, so it stays multicolour in both themes. +- **ServiceNowIcon** — Trademark guidelines require the mark in the graphic form provided. +- **ShopifyIcon** — The "S" stays white regardless of background; no gradients, shadows or recolouring. +- **ShortcutIcon** — The mark "is used across various colors but never changes its visual structure." +- **ShutterstockIcon** — Brand rule: the logo is only ever black or white. +- **SigNozIcon** — SigNoz ships no reversed variant; the tile mark is used unchanged on light and dark. +- **Slack** — Fixed full-colour mark, no per-theme variant. +- **SmartsheetIcon** — Those are two of the approved logo colorways; the guide forbids any other recolouring. +- **SnowflakeIcon** — Snowflake Blue is the only approved logo color; the sole alternate is a white reverse reserved for full-bleed Snowflake Blue. +- **SpeechifyIcon** — The blue logomark is reserved for white backgrounds; every other background takes the black or white monochrome version. +- **SplitwiseIcon** — Splitwise's logos carry a single green and no reversed variant, so the same colour is used on light and dark. +- **SpotifyIcon** — Spotify permits the green icon only on black or white backgrounds and requires the white monochrome colourway on any other dark background, so the pair is fixed rather than caller-set. +- **SquareIcon** — Square ships only black and white logo files and states "Do not change the color", so no tinted variant is allowed. +- **StraleIcon** — Strale's own logo component fills with currentColor, so the mark is meant to take the theme's foreground. +- **StravaIcon** — Strava's guidelines forbid modifying or altering its logos, and the orange Echelon is the mark Strava itself uses on light and dark alike. +- **StripeIcon** — Stripe's Marks Usage Terms forbid altering the marks, so this ships verbatim in both themes rather than as a recoloured pair. +- **SupabaseIcon** — Forbids modifying or recolouring the mark. +- **SurrealdbIcon** — Same gradient mark on light and dark; monochrome variants are for subtle placements only. +- **SvelteIcon** — Its guidelines count the official colour scheme as part of the mark — do not recolour. +- **TaskadeIcon** — Brand forbids recolouring, so only those two published variants are used. +- **TelegramIcon** — The shaded-plane drawing is Telegram's retired Logo_old. +- **TerraIcon** — The outlines are part of the artwork and stay black in both themes; the blue T carries the mark on dark backgrounds. +- **TheirStackIcon** — The mark ships black-only, but the same brand rules forbid placing it on low-contrast backgrounds; on a dark surface black is 1.68:1, so it is tinted to the brand's own white. +- **ThreadsIcon** — The pack ships the mark in black and white only, so it must never be tinted. +- **TogglIcon** — Toggl requires the mark be used as is, unmodified. +- **TomorrowIoIcon** — Their stylesheet reverses only the wordmark on dark headers (path.logo-letter{fill:#fff}); the mark itself stays logo blue in both themes. +- **TrelloIcon** — Atlassian: never compose your own versions or deconstruct official assets. +- **TripadvisorIcon** — Dark backgrounds take Tripadvisor's separate outlined Ollie, never an inverted or recoloured one. +- **TwilioIcon** — Twilio reserves its corporate logo for permitted use and forbids recreating or modifying it. +- **TwitchIcon** — Trademark guidelines forbid recolouring. +- **TwitterIcon** — The component draws the X glyph, not the legacy bird. +- **TypeformIcon** — Default brand colours are "Paper (white) and Ink (black)". +- **UltravoxIcon** — Gradient mark; ultravox.ai links that same asset for both prefers-color-scheme light and dark, so there is no per-theme variant. +- **VectaraIcon** — #7E00FF → #07FEEE iridescent sweep, sampled from the logo mark on vectara.com (no brand kit is published). Vectara reserves its trademarks: do not recolour. +- **VercelIcon** — Vercel ships only light-theme (black) and dark-theme (white) triangle marks and explicitly forbids modifying or recoloring the trademarks. +- **VismaIcon** — Positive black on light, negative white on dark; the logo may not be given any other colour. +- **VueIcon** — Vue's dark-background variant is separate outlined artwork rather than a recolour, so the two-tone mark is used in both themes. +- **WebflowIcon** — Mark ships in blue, black or white only. +- **WhatsappBusinessIcon** — "You shouldn't modify any colors in our logos." +- **WooCommerceIcon** — Automattic requires the mark in its exact, most up-to-date form, so only their two published colorways are used, never a recolour. +- **WordpressIcon** — Every official logotype vector is BaseGray #32373C, shipped alongside a White/transparent version for dark backgrounds. +- **XataIcon** — Brand forbids recolouring, and the full-colour symbol is the same purple in light and dark modes. +- **XeroIcon** — Single-colour mark: white wordmark on the blue badge in both themes. +- **YamlIcon** — YAML publishes no reversed variant, and its black letters would be invisible on the dark surface. +- **YelpIcon** — Yelp forbids altering the logos and requires the ® to accompany the mark at all times. +- **YoutubeIcon** — "The triangle in the full-color red icon must always be white." +- **ZendeskIcon** — Zendesk's brand guidelines specify the logo in Licorice #11110D and Coconut #FFFFFF only and forbid unapproved color variations. +- **ZeroTierIcon** — The tile is identical on light and dark; only the wordmark inverts. +- **ZitadelIcon** — The gradient chevrons stay #FF8F00→#FE00FF in both variants. +- **ZohoIcon** — The four squares carry their own brand hexes (#E42527/#089949/#226DB4/#F9B21D) on light; Zoho's reversed lock-up is entirely white, so they invert with the wordmark on dark. +- **ZoomIcon** — The logo "may only be used in Bloom (#0B5CFF), White, or Black", with White reserved for dark backgrounds and Black requiring prior brand approval. +- **ZuploIcon** — Brand guidelines forbid recolouring the mark; pink is the official variant on both light and dark surfaces. + +## Concept icons + +Not brands. These inherit `currentColor` on purpose and must not be given a pair. + +`AgentInstructionsIcon`, `AiAgentIcon`, `ApiKeyAuthIcon`, `AssetDatabaseIcon`, `AssetDucklakeIcon`, `AssetGenericIcon`, `AssetResIcon`, `AssetS3Icon`, `BarsStaggered`, `BasicHttpAuthIcon`, `BcryptIcon`, `CACertificate`, `CustomAiIcon`, `DbIcon`, `FormInputIcon`, `FunnelCog`, `GpgKeyIcon`, `HttpIcon`, `JsonSchemaIcon`, `LdapIcon`, `Mail`, `OauthIcon`, `PaintbrushOff`, `QRCodeIcon`, `QuestionInputIcon`, `RecordIcon`, `RestIcon`, `SchedulePollIcon`, `SignatureAuthIcon`, `SparklesOffIcon`, `WebdavIcon`, `WindmillAiIcon`, `WindmillIcon`, `WindmillIcon2` + +## Coverage + +- brand icons: **314**, of which **310** carry a recorded source +- per-theme pairs applied: **136** (5 of them by inversion or a two-SVG swap, see above) +- concept icons: **34** +- effectively invisible on light: **1** (AbstractApiIcon) +- effectively invisible on dark: **2** (PaychexIcon, TripadvisorIcon) diff --git a/frontend/src/lib/components/icons/BambooHrIcon.svelte b/frontend/src/lib/components/icons/BambooHrIcon.svelte index efbeec2ece..534b48cbe6 100644 --- a/frontend/src/lib/components/icons/BambooHrIcon.svelte +++ b/frontend/src/lib/components/icons/BambooHrIcon.svelte @@ -1,12 +1,27 @@ - - + + + + + + diff --git a/frontend/src/lib/components/icons/BaremetricsIcon.svelte b/frontend/src/lib/components/icons/BaremetricsIcon.svelte index be550a88fc..a05a84c7c9 100644 --- a/frontend/src/lib/components/icons/BaremetricsIcon.svelte +++ b/frontend/src/lib/components/icons/BaremetricsIcon.svelte @@ -1,12 +1,15 @@ - - + + + diff --git a/frontend/src/lib/components/icons/BaserowIcon.svelte b/frontend/src/lib/components/icons/BaserowIcon.svelte new file mode 100644 index 0000000000..5d51c8cfa5 --- /dev/null +++ b/frontend/src/lib/components/icons/BaserowIcon.svelte @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/frontend/src/lib/components/icons/BasicHttpAuthIcon.svelte b/frontend/src/lib/components/icons/BasicHttpAuthIcon.svelte new file mode 100644 index 0000000000..2a570cfb3e --- /dev/null +++ b/frontend/src/lib/components/icons/BasicHttpAuthIcon.svelte @@ -0,0 +1,25 @@ + + + + + + + + diff --git a/frontend/src/lib/components/icons/BasisTheoryIcon.svelte b/frontend/src/lib/components/icons/BasisTheoryIcon.svelte new file mode 100644 index 0000000000..6f2dfc2d4e --- /dev/null +++ b/frontend/src/lib/components/icons/BasisTheoryIcon.svelte @@ -0,0 +1,26 @@ + + + + + + + diff --git a/frontend/src/lib/components/icons/BcryptIcon.svelte b/frontend/src/lib/components/icons/BcryptIcon.svelte index bf9bf9b6a2..be79ba1418 100644 --- a/frontend/src/lib/components/icons/BcryptIcon.svelte +++ b/frontend/src/lib/components/icons/BcryptIcon.svelte @@ -1,15 +1,39 @@ - - - - + + + + diff --git a/frontend/src/lib/components/icons/BeamerIcon.svelte b/frontend/src/lib/components/icons/BeamerIcon.svelte new file mode 100644 index 0000000000..696eac11f4 --- /dev/null +++ b/frontend/src/lib/components/icons/BeamerIcon.svelte @@ -0,0 +1,35 @@ + + + + + + + + + + + diff --git a/frontend/src/lib/components/icons/BigQueryIcon.svelte b/frontend/src/lib/components/icons/BigQueryIcon.svelte index 10d099f75d..43958d302e 100644 --- a/frontend/src/lib/components/icons/BigQueryIcon.svelte +++ b/frontend/src/lib/components/icons/BigQueryIcon.svelte @@ -1,42 +1,36 @@ + -Icon_24px_BigQuery_Color + + + + + + + + diff --git a/frontend/src/lib/components/icons/BitbucketIcon.svelte b/frontend/src/lib/components/icons/BitbucketIcon.svelte index 9b33e6bb10..ca368edc70 100644 --- a/frontend/src/lib/components/icons/BitbucketIcon.svelte +++ b/frontend/src/lib/components/icons/BitbucketIcon.svelte @@ -1,24 +1,23 @@ - - - - - - - - Bitbucket-blue - - - - - - - \ No newline at end of file + + + + diff --git a/frontend/src/lib/components/icons/BitlyIcon.svelte b/frontend/src/lib/components/icons/BitlyIcon.svelte index 3cc3b43122..a023981cec 100644 --- a/frontend/src/lib/components/icons/BitlyIcon.svelte +++ b/frontend/src/lib/components/icons/BitlyIcon.svelte @@ -1,12 +1,21 @@ + - - + + diff --git a/frontend/src/lib/components/icons/BloggerIcon.svelte b/frontend/src/lib/components/icons/BloggerIcon.svelte index ab1d2d262f..13ec49a1c0 100644 --- a/frontend/src/lib/components/icons/BloggerIcon.svelte +++ b/frontend/src/lib/components/icons/BloggerIcon.svelte @@ -1,12 +1,24 @@ + - - + + + + diff --git a/frontend/src/lib/components/icons/BlueskyIcon.svelte b/frontend/src/lib/components/icons/BlueskyIcon.svelte index 1f8545e325..68dea0f3d2 100644 --- a/frontend/src/lib/components/icons/BlueskyIcon.svelte +++ b/frontend/src/lib/components/icons/BlueskyIcon.svelte @@ -1,12 +1,25 @@ - - + + + diff --git a/frontend/src/lib/components/icons/BotifyIcon.svelte b/frontend/src/lib/components/icons/BotifyIcon.svelte new file mode 100644 index 0000000000..375e128ac4 --- /dev/null +++ b/frontend/src/lib/components/icons/BotifyIcon.svelte @@ -0,0 +1,16 @@ + + + + + + diff --git a/frontend/src/lib/components/icons/BoxIcon.svelte b/frontend/src/lib/components/icons/BoxIcon.svelte index a6a5d5dc07..980d8f5757 100644 --- a/frontend/src/lib/components/icons/BoxIcon.svelte +++ b/frontend/src/lib/components/icons/BoxIcon.svelte @@ -1,12 +1,23 @@ - - + + + diff --git a/frontend/src/lib/components/icons/BrandLetterIcon.svelte b/frontend/src/lib/components/icons/BrandLetterIcon.svelte new file mode 100644 index 0000000000..a9e9fa2966 --- /dev/null +++ b/frontend/src/lib/components/icons/BrandLetterIcon.svelte @@ -0,0 +1,58 @@ + + + + + + {letter} + diff --git a/frontend/src/lib/components/icons/BrevoIcon.svelte b/frontend/src/lib/components/icons/BrevoIcon.svelte index 2907c346f7..971fb5d9f3 100644 --- a/frontend/src/lib/components/icons/BrevoIcon.svelte +++ b/frontend/src/lib/components/icons/BrevoIcon.svelte @@ -1,12 +1,20 @@ - - + + + + diff --git a/frontend/src/lib/components/icons/BrexIcon.svelte b/frontend/src/lib/components/icons/BrexIcon.svelte index 5f9fbf8de1..526b2fe536 100644 --- a/frontend/src/lib/components/icons/BrexIcon.svelte +++ b/frontend/src/lib/components/icons/BrexIcon.svelte @@ -1,12 +1,22 @@ - - + + + diff --git a/frontend/src/lib/components/icons/BrowserlessIcon.svelte b/frontend/src/lib/components/icons/BrowserlessIcon.svelte index 768d51145c..9dcf785adf 100644 --- a/frontend/src/lib/components/icons/BrowserlessIcon.svelte +++ b/frontend/src/lib/components/icons/BrowserlessIcon.svelte @@ -1,12 +1,22 @@ - - + + + diff --git a/frontend/src/lib/components/icons/BubbleIcon.svelte b/frontend/src/lib/components/icons/BubbleIcon.svelte index fdfb049e70..15277d5436 100644 --- a/frontend/src/lib/components/icons/BubbleIcon.svelte +++ b/frontend/src/lib/components/icons/BubbleIcon.svelte @@ -1,16 +1,24 @@ + - - - - - + + + + diff --git a/frontend/src/lib/components/icons/BuildkiteIcon.svelte b/frontend/src/lib/components/icons/BuildkiteIcon.svelte index 282d782e31..e510c61449 100644 --- a/frontend/src/lib/components/icons/BuildkiteIcon.svelte +++ b/frontend/src/lib/components/icons/BuildkiteIcon.svelte @@ -1,12 +1,16 @@ - - + + + + + + diff --git a/frontend/src/lib/components/icons/BunIcon.svelte b/frontend/src/lib/components/icons/BunIcon.svelte index ef845d4834..792ad10b3c 100644 --- a/frontend/src/lib/components/icons/BunIcon.svelte +++ b/frontend/src/lib/components/icons/BunIcon.svelte @@ -1,12 +1,13 @@ + + interface Props { + height?: string + width?: string + } + + let { height = '24px', width = '24px' }: Props = $props() + + + + + + diff --git a/frontend/src/lib/components/icons/CACertificate.svelte b/frontend/src/lib/components/icons/CACertificate.svelte index c73aa5d3ca..d6a6a6cdde 100644 --- a/frontend/src/lib/components/icons/CACertificate.svelte +++ b/frontend/src/lib/components/icons/CACertificate.svelte @@ -1,12 +1,17 @@ - - \ No newline at end of file + + diff --git a/frontend/src/lib/components/icons/CSharpIcon.svelte b/frontend/src/lib/components/icons/CSharpIcon.svelte index 703c692036..c9c06d77cb 100644 --- a/frontend/src/lib/components/icons/CSharpIcon.svelte +++ b/frontend/src/lib/components/icons/CSharpIcon.svelte @@ -1,18 +1,41 @@ - - - - - - - - + + + + + + + + + + diff --git a/frontend/src/lib/components/icons/CalcomIcon.svelte b/frontend/src/lib/components/icons/CalcomIcon.svelte index 382b8e7a56..5dea23818d 100644 --- a/frontend/src/lib/components/icons/CalcomIcon.svelte +++ b/frontend/src/lib/components/icons/CalcomIcon.svelte @@ -1,13 +1,16 @@ + interface Props { - height?: string; - width?: string; + height?: string + width?: string } - let { height = '24px', width = '24px' }: Props = $props(); + let { height = '24px', width = '24px' }: Props = $props() - - + + + diff --git a/frontend/src/lib/components/icons/CampaynIcon.svelte b/frontend/src/lib/components/icons/CampaynIcon.svelte new file mode 100644 index 0000000000..c343cb186f --- /dev/null +++ b/frontend/src/lib/components/icons/CampaynIcon.svelte @@ -0,0 +1,30 @@ + + + + + + + + + + diff --git a/frontend/src/lib/components/icons/CertopusIcon.svelte b/frontend/src/lib/components/icons/CertopusIcon.svelte new file mode 100644 index 0000000000..227528bae2 --- /dev/null +++ b/frontend/src/lib/components/icons/CertopusIcon.svelte @@ -0,0 +1,38 @@ + + + + + + + + + + + diff --git a/frontend/src/lib/components/icons/ChromaIcon.svelte b/frontend/src/lib/components/icons/ChromaIcon.svelte new file mode 100644 index 0000000000..b42a6b58e4 --- /dev/null +++ b/frontend/src/lib/components/icons/ChromaIcon.svelte @@ -0,0 +1,22 @@ + + + + + + + + + + diff --git a/frontend/src/lib/components/icons/CircleCiIcon.svelte b/frontend/src/lib/components/icons/CircleCiIcon.svelte index 92402ac6cd..59c97c4e37 100644 --- a/frontend/src/lib/components/icons/CircleCiIcon.svelte +++ b/frontend/src/lib/components/icons/CircleCiIcon.svelte @@ -1,12 +1,23 @@ - - + + + diff --git a/frontend/src/lib/components/icons/CiscoIcon.svelte b/frontend/src/lib/components/icons/CiscoIcon.svelte index d2d4b14bd8..aa3ed22d0f 100644 --- a/frontend/src/lib/components/icons/CiscoIcon.svelte +++ b/frontend/src/lib/components/icons/CiscoIcon.svelte @@ -1,12 +1,22 @@ - - + + + diff --git a/frontend/src/lib/components/icons/ClaudeIcon.svelte b/frontend/src/lib/components/icons/ClaudeIcon.svelte index 5c5cdeffe5..44ec5723c2 100644 --- a/frontend/src/lib/components/icons/ClaudeIcon.svelte +++ b/frontend/src/lib/components/icons/ClaudeIcon.svelte @@ -1,12 +1,13 @@ + interface Props { - height?: string; - width?: string; + height?: string + width?: string } - let { height = '24px', width = '24px' }: Props = $props(); + let { height = '24px', width = '24px' }: Props = $props() + - + diff --git a/frontend/src/lib/components/icons/ClerkIcon.svelte b/frontend/src/lib/components/icons/ClerkIcon.svelte index 21baeae38d..9649794578 100644 --- a/frontend/src/lib/components/icons/ClerkIcon.svelte +++ b/frontend/src/lib/components/icons/ClerkIcon.svelte @@ -1,12 +1,24 @@ + - - + + + + diff --git a/frontend/src/lib/components/icons/ClickhouseIcon.svelte b/frontend/src/lib/components/icons/ClickhouseIcon.svelte index 6d3870762a..a1ea211f36 100644 --- a/frontend/src/lib/components/icons/ClickhouseIcon.svelte +++ b/frontend/src/lib/components/icons/ClickhouseIcon.svelte @@ -1,27 +1,36 @@ + - \ No newline at end of file + + + + diff --git a/frontend/src/lib/components/icons/ClickupIcon.svelte b/frontend/src/lib/components/icons/ClickupIcon.svelte index 22e160931a..e84ddc0633 100644 --- a/frontend/src/lib/components/icons/ClickupIcon.svelte +++ b/frontend/src/lib/components/icons/ClickupIcon.svelte @@ -1,10 +1,11 @@ + - - + + - + diff --git a/frontend/src/lib/components/icons/CloseIcon.svelte b/frontend/src/lib/components/icons/CloseIcon.svelte index 73e6f414f9..e61b7ada6c 100644 --- a/frontend/src/lib/components/icons/CloseIcon.svelte +++ b/frontend/src/lib/components/icons/CloseIcon.svelte @@ -1,42 +1,37 @@ + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + diff --git a/frontend/src/lib/components/icons/CloudflareIcon.svelte b/frontend/src/lib/components/icons/CloudflareIcon.svelte index 6ade506d8a..7fed848d2d 100644 --- a/frontend/src/lib/components/icons/CloudflareIcon.svelte +++ b/frontend/src/lib/components/icons/CloudflareIcon.svelte @@ -1,12 +1,13 @@ + - diff --git a/frontend/src/lib/components/icons/CloudinaryIcon.svelte b/frontend/src/lib/components/icons/CloudinaryIcon.svelte index a61f3bbe7c..9dc633dccd 100644 --- a/frontend/src/lib/components/icons/CloudinaryIcon.svelte +++ b/frontend/src/lib/components/icons/CloudinaryIcon.svelte @@ -1,12 +1,22 @@ - - + + + diff --git a/frontend/src/lib/components/icons/CockroachDbIcon.svelte b/frontend/src/lib/components/icons/CockroachDbIcon.svelte index d88f7c52de..2953c152f5 100644 --- a/frontend/src/lib/components/icons/CockroachDbIcon.svelte +++ b/frontend/src/lib/components/icons/CockroachDbIcon.svelte @@ -1,12 +1,24 @@ - - + + + diff --git a/frontend/src/lib/components/icons/CodaIcon.svelte b/frontend/src/lib/components/icons/CodaIcon.svelte index d8486224d9..9a58fb8998 100644 --- a/frontend/src/lib/components/icons/CodaIcon.svelte +++ b/frontend/src/lib/components/icons/CodaIcon.svelte @@ -1,12 +1,15 @@ + - + diff --git a/frontend/src/lib/components/icons/CodatIcon.svelte b/frontend/src/lib/components/icons/CodatIcon.svelte new file mode 100644 index 0000000000..732a4a878e --- /dev/null +++ b/frontend/src/lib/components/icons/CodatIcon.svelte @@ -0,0 +1,32 @@ + + + + + + + + + + + diff --git a/frontend/src/lib/components/icons/CohereIcon.svelte b/frontend/src/lib/components/icons/CohereIcon.svelte index c0a4aea8ee..dba01ef6ed 100644 --- a/frontend/src/lib/components/icons/CohereIcon.svelte +++ b/frontend/src/lib/components/icons/CohereIcon.svelte @@ -1,21 +1,35 @@ + - - - - - - - - - - + + + + + + + + + + diff --git a/frontend/src/lib/components/icons/CoinMarketCapIcon.svelte b/frontend/src/lib/components/icons/CoinMarketCapIcon.svelte index 770b60f40a..67ff2998c8 100644 --- a/frontend/src/lib/components/icons/CoinMarketCapIcon.svelte +++ b/frontend/src/lib/components/icons/CoinMarketCapIcon.svelte @@ -1,12 +1,22 @@ - - + + + diff --git a/frontend/src/lib/components/icons/CoinbaseIcon.svelte b/frontend/src/lib/components/icons/CoinbaseIcon.svelte index b58a45fff0..5d61e83df7 100644 --- a/frontend/src/lib/components/icons/CoinbaseIcon.svelte +++ b/frontend/src/lib/components/icons/CoinbaseIcon.svelte @@ -1,12 +1,23 @@ + - - + + diff --git a/frontend/src/lib/components/icons/ComapeoIcon.svelte b/frontend/src/lib/components/icons/ComapeoIcon.svelte new file mode 100644 index 0000000000..062c45e087 --- /dev/null +++ b/frontend/src/lib/components/icons/ComapeoIcon.svelte @@ -0,0 +1,25 @@ + + + + + + diff --git a/frontend/src/lib/components/icons/ConfluenceIcon.svelte b/frontend/src/lib/components/icons/ConfluenceIcon.svelte index 60dbcef880..a892e912f2 100644 --- a/frontend/src/lib/components/icons/ConfluenceIcon.svelte +++ b/frontend/src/lib/components/icons/ConfluenceIcon.svelte @@ -1,12 +1,17 @@ + - - + + + diff --git a/frontend/src/lib/components/icons/ContentfulIcon.svelte b/frontend/src/lib/components/icons/ContentfulIcon.svelte index 2af55d48fc..38f466d92d 100644 --- a/frontend/src/lib/components/icons/ContentfulIcon.svelte +++ b/frontend/src/lib/components/icons/ContentfulIcon.svelte @@ -1,12 +1,32 @@ + - - + + + + + + diff --git a/frontend/src/lib/components/icons/ContiguityIcon.svelte b/frontend/src/lib/components/icons/ContiguityIcon.svelte new file mode 100644 index 0000000000..30de7f9c13 --- /dev/null +++ b/frontend/src/lib/components/icons/ContiguityIcon.svelte @@ -0,0 +1,28 @@ + + + + + + + diff --git a/frontend/src/lib/components/icons/ConvertKitIcon.svelte b/frontend/src/lib/components/icons/ConvertKitIcon.svelte index 2fbcf0e53f..0506f62f43 100644 --- a/frontend/src/lib/components/icons/ConvertKitIcon.svelte +++ b/frontend/src/lib/components/icons/ConvertKitIcon.svelte @@ -1,12 +1,24 @@ - - + + + + + diff --git a/frontend/src/lib/components/icons/CoupaIcon.svelte b/frontend/src/lib/components/icons/CoupaIcon.svelte index 4d4058fdac..a8c780bdbc 100644 --- a/frontend/src/lib/components/icons/CoupaIcon.svelte +++ b/frontend/src/lib/components/icons/CoupaIcon.svelte @@ -1,19 +1,22 @@ + diff --git a/frontend/src/lib/components/icons/CustomAiIcon.svelte b/frontend/src/lib/components/icons/CustomAiIcon.svelte new file mode 100644 index 0000000000..a646753b7c --- /dev/null +++ b/frontend/src/lib/components/icons/CustomAiIcon.svelte @@ -0,0 +1,27 @@ + + + + + + + + diff --git a/frontend/src/lib/components/icons/DatabricksIcon.svelte b/frontend/src/lib/components/icons/DatabricksIcon.svelte index e767337442..e06471cdd4 100644 --- a/frontend/src/lib/components/icons/DatabricksIcon.svelte +++ b/frontend/src/lib/components/icons/DatabricksIcon.svelte @@ -1,12 +1,13 @@ + interface Props { - height?: string; - width?: string; + height?: string + width?: string } - let { height = '24px', width = '24px' }: Props = $props(); + let { height = '24px', width = '24px' }: Props = $props() + + + diff --git a/frontend/src/lib/components/icons/DatoCmsIcon.svelte b/frontend/src/lib/components/icons/DatoCmsIcon.svelte index d462832e17..e2e4b2be0e 100644 --- a/frontend/src/lib/components/icons/DatoCmsIcon.svelte +++ b/frontend/src/lib/components/icons/DatoCmsIcon.svelte @@ -1,12 +1,15 @@ + - + diff --git a/frontend/src/lib/components/icons/DbIcon.svelte b/frontend/src/lib/components/icons/DbIcon.svelte index fe74ca9adb..44227ad43a 100644 --- a/frontend/src/lib/components/icons/DbIcon.svelte +++ b/frontend/src/lib/components/icons/DbIcon.svelte @@ -1,17 +1,17 @@ diff --git a/frontend/src/lib/components/icons/DbtIcon.svelte b/frontend/src/lib/components/icons/DbtIcon.svelte index b647cb215e..a90f82b90f 100644 --- a/frontend/src/lib/components/icons/DbtIcon.svelte +++ b/frontend/src/lib/components/icons/DbtIcon.svelte @@ -7,21 +7,18 @@ let { height = 24, width = 24 }: Props = $props() + - - - - - - - - + diff --git a/frontend/src/lib/components/icons/DeelIcon.svelte b/frontend/src/lib/components/icons/DeelIcon.svelte index 0b93a4a98f..e36b64fe3f 100644 --- a/frontend/src/lib/components/icons/DeelIcon.svelte +++ b/frontend/src/lib/components/icons/DeelIcon.svelte @@ -1,12 +1,25 @@ - - + + + + diff --git a/frontend/src/lib/components/icons/DeepInfraIcon.svelte b/frontend/src/lib/components/icons/DeepInfraIcon.svelte new file mode 100644 index 0000000000..3750b5ff42 --- /dev/null +++ b/frontend/src/lib/components/icons/DeepInfraIcon.svelte @@ -0,0 +1,27 @@ + + + + + + diff --git a/frontend/src/lib/components/icons/DeepLIcon.svelte b/frontend/src/lib/components/icons/DeepLIcon.svelte index 028b061dc0..58b2a14833 100644 --- a/frontend/src/lib/components/icons/DeepLIcon.svelte +++ b/frontend/src/lib/components/icons/DeepLIcon.svelte @@ -1,12 +1,40 @@ + - - + + + + diff --git a/frontend/src/lib/components/icons/DeepSeekIcon.svelte b/frontend/src/lib/components/icons/DeepSeekIcon.svelte index d0ba0c6423..560c5704bb 100644 --- a/frontend/src/lib/components/icons/DeepSeekIcon.svelte +++ b/frontend/src/lib/components/icons/DeepSeekIcon.svelte @@ -7,7 +7,15 @@ let { height = '24px', width = '24px' }: Props = $props() - + + diff --git a/frontend/src/lib/components/icons/DenoIcon.svelte b/frontend/src/lib/components/icons/DenoIcon.svelte index 86dca42410..776144b34f 100644 --- a/frontend/src/lib/components/icons/DenoIcon.svelte +++ b/frontend/src/lib/components/icons/DenoIcon.svelte @@ -1,41 +1,44 @@ + + + + + diff --git a/frontend/src/lib/components/icons/DigitalOceanIcon.svelte b/frontend/src/lib/components/icons/DigitalOceanIcon.svelte index 206e395175..6daeb458d0 100644 --- a/frontend/src/lib/components/icons/DigitalOceanIcon.svelte +++ b/frontend/src/lib/components/icons/DigitalOceanIcon.svelte @@ -1,12 +1,15 @@ + - + diff --git a/frontend/src/lib/components/icons/DiscordIcon.svelte b/frontend/src/lib/components/icons/DiscordIcon.svelte index 384008b773..ca92f30ed4 100644 --- a/frontend/src/lib/components/icons/DiscordIcon.svelte +++ b/frontend/src/lib/components/icons/DiscordIcon.svelte @@ -1,21 +1,22 @@ + + + diff --git a/frontend/src/lib/components/icons/DiscourseIcon.svelte b/frontend/src/lib/components/icons/DiscourseIcon.svelte index fd1e53f9a3..dd590cee4a 100644 --- a/frontend/src/lib/components/icons/DiscourseIcon.svelte +++ b/frontend/src/lib/components/icons/DiscourseIcon.svelte @@ -1,12 +1,38 @@ - - + + + diff --git a/frontend/src/lib/components/icons/DocSpringIcon.svelte b/frontend/src/lib/components/icons/DocSpringIcon.svelte new file mode 100644 index 0000000000..f67ba12c70 --- /dev/null +++ b/frontend/src/lib/components/icons/DocSpringIcon.svelte @@ -0,0 +1,55 @@ + + + + + + + + + + + + + diff --git a/frontend/src/lib/components/icons/DockerIcon.svelte b/frontend/src/lib/components/icons/DockerIcon.svelte index d7ab3f498c..f5a5ac11a5 100644 --- a/frontend/src/lib/components/icons/DockerIcon.svelte +++ b/frontend/src/lib/components/icons/DockerIcon.svelte @@ -1,27 +1,22 @@ + diff --git a/frontend/src/lib/components/icons/DocusignIcon.svelte b/frontend/src/lib/components/icons/DocusignIcon.svelte index 91d96bac36..5fe8c42f82 100644 --- a/frontend/src/lib/components/icons/DocusignIcon.svelte +++ b/frontend/src/lib/components/icons/DocusignIcon.svelte @@ -1,12 +1,30 @@ + - - + + + + diff --git a/frontend/src/lib/components/icons/DropboxIcon.svelte b/frontend/src/lib/components/icons/DropboxIcon.svelte index 3facd1dda2..51821fdd3e 100644 --- a/frontend/src/lib/components/icons/DropboxIcon.svelte +++ b/frontend/src/lib/components/icons/DropboxIcon.svelte @@ -1,12 +1,15 @@ - - + + + diff --git a/frontend/src/lib/components/icons/DuckDbIcon.svelte b/frontend/src/lib/components/icons/DuckDbIcon.svelte index 1fa2e55b2c..793827c981 100644 --- a/frontend/src/lib/components/icons/DuckDbIcon.svelte +++ b/frontend/src/lib/components/icons/DuckDbIcon.svelte @@ -1,23 +1,32 @@ - + + diff --git a/frontend/src/lib/components/icons/DucklakeIcon.svelte b/frontend/src/lib/components/icons/DucklakeIcon.svelte index 488260ea1a..5665734ff5 100644 --- a/frontend/src/lib/components/icons/DucklakeIcon.svelte +++ b/frontend/src/lib/components/icons/DucklakeIcon.svelte @@ -1,4 +1,5 @@ + + interface Props { + height?: string + width?: string + } + + let { height = '24px', width = '24px' }: Props = $props() + + + + + + + + + + + + diff --git a/frontend/src/lib/components/icons/DynatraceIcon.svelte b/frontend/src/lib/components/icons/DynatraceIcon.svelte index e8d57c58ce..df0ad401a4 100644 --- a/frontend/src/lib/components/icons/DynatraceIcon.svelte +++ b/frontend/src/lib/components/icons/DynatraceIcon.svelte @@ -1,31 +1,53 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + diff --git a/frontend/src/lib/components/icons/EdgeDbIcon.svelte b/frontend/src/lib/components/icons/EdgeDbIcon.svelte index cc31f328d4..19cb62e2b3 100644 --- a/frontend/src/lib/components/icons/EdgeDbIcon.svelte +++ b/frontend/src/lib/components/icons/EdgeDbIcon.svelte @@ -1,3 +1,6 @@ + - + - diff --git a/frontend/src/lib/components/icons/EnodeIcon.svelte b/frontend/src/lib/components/icons/EnodeIcon.svelte new file mode 100644 index 0000000000..91a5f59094 --- /dev/null +++ b/frontend/src/lib/components/icons/EnodeIcon.svelte @@ -0,0 +1,28 @@ + + + + + + + + diff --git a/frontend/src/lib/components/icons/EventbriteIcon.svelte b/frontend/src/lib/components/icons/EventbriteIcon.svelte index f851a933f3..c98db1071e 100644 --- a/frontend/src/lib/components/icons/EventbriteIcon.svelte +++ b/frontend/src/lib/components/icons/EventbriteIcon.svelte @@ -1,15 +1,16 @@ - - - - - + + + diff --git a/frontend/src/lib/components/icons/ExaIcon.svelte b/frontend/src/lib/components/icons/ExaIcon.svelte new file mode 100644 index 0000000000..eef3057e92 --- /dev/null +++ b/frontend/src/lib/components/icons/ExaIcon.svelte @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/frontend/src/lib/components/icons/FaunadbIcon.svelte b/frontend/src/lib/components/icons/FaunadbIcon.svelte index 62c4dde4f2..7bb3f371e3 100644 --- a/frontend/src/lib/components/icons/FaunadbIcon.svelte +++ b/frontend/src/lib/components/icons/FaunadbIcon.svelte @@ -1,13 +1,15 @@ + - \ No newline at end of file diff --git a/frontend/src/lib/components/icons/FigmaIcon.svelte b/frontend/src/lib/components/icons/FigmaIcon.svelte index e31fe0a71d..bc39256ab2 100644 --- a/frontend/src/lib/components/icons/FigmaIcon.svelte +++ b/frontend/src/lib/components/icons/FigmaIcon.svelte @@ -1,12 +1,20 @@ + - - + + + + + + diff --git a/frontend/src/lib/components/icons/FirebaseIcon.svelte b/frontend/src/lib/components/icons/FirebaseIcon.svelte index 6d1ce00468..fd85fc72d2 100644 --- a/frontend/src/lib/components/icons/FirebaseIcon.svelte +++ b/frontend/src/lib/components/icons/FirebaseIcon.svelte @@ -1,10 +1,11 @@ + - - - - - - - - - - diff --git a/frontend/src/lib/components/icons/FlyIcon.svelte b/frontend/src/lib/components/icons/FlyIcon.svelte index 02118212b2..d48b62556e 100644 --- a/frontend/src/lib/components/icons/FlyIcon.svelte +++ b/frontend/src/lib/components/icons/FlyIcon.svelte @@ -1,12 +1,22 @@ - - + + + diff --git a/frontend/src/lib/components/icons/FormInputIcon.svelte b/frontend/src/lib/components/icons/FormInputIcon.svelte new file mode 100644 index 0000000000..1f1dcbc242 --- /dev/null +++ b/frontend/src/lib/components/icons/FormInputIcon.svelte @@ -0,0 +1,26 @@ + + + + + + + + + diff --git a/frontend/src/lib/components/icons/FormstackIcon.svelte b/frontend/src/lib/components/icons/FormstackIcon.svelte new file mode 100644 index 0000000000..e0da265e6a --- /dev/null +++ b/frontend/src/lib/components/icons/FormstackIcon.svelte @@ -0,0 +1,15 @@ + + + + + + diff --git a/frontend/src/lib/components/icons/FoxentryIcon.svelte b/frontend/src/lib/components/icons/FoxentryIcon.svelte new file mode 100644 index 0000000000..88f6b71054 --- /dev/null +++ b/frontend/src/lib/components/icons/FoxentryIcon.svelte @@ -0,0 +1,20 @@ + + + + + + diff --git a/frontend/src/lib/components/icons/FreshdeskIcon.svelte b/frontend/src/lib/components/icons/FreshdeskIcon.svelte index 72f2d7b1fa..bbeb87d668 100644 --- a/frontend/src/lib/components/icons/FreshdeskIcon.svelte +++ b/frontend/src/lib/components/icons/FreshdeskIcon.svelte @@ -1,12 +1,19 @@ + - + diff --git a/frontend/src/lib/components/icons/FrontAppIcon.svelte b/frontend/src/lib/components/icons/FrontAppIcon.svelte index dfba63c8b7..aba4bccae4 100644 --- a/frontend/src/lib/components/icons/FrontAppIcon.svelte +++ b/frontend/src/lib/components/icons/FrontAppIcon.svelte @@ -1,12 +1,16 @@ - - + + + + diff --git a/frontend/src/lib/components/icons/FunkwhaleIcon.svelte b/frontend/src/lib/components/icons/FunkwhaleIcon.svelte index 45a1821beb..0fb36804f6 100644 --- a/frontend/src/lib/components/icons/FunkwhaleIcon.svelte +++ b/frontend/src/lib/components/icons/FunkwhaleIcon.svelte @@ -1,73 +1,45 @@ + image/svg+xml + - - - - - - - - - - - - diff --git a/frontend/src/lib/components/icons/GCloudIcon.svelte b/frontend/src/lib/components/icons/GCloudIcon.svelte deleted file mode 100644 index 6fba8faa05..0000000000 --- a/frontend/src/lib/components/icons/GCloudIcon.svelte +++ /dev/null @@ -1,21 +0,0 @@ - - - diff --git a/frontend/src/lib/components/icons/GSheetsIcon.svelte b/frontend/src/lib/components/icons/GSheetsIcon.svelte index 419f9d1b26..b23010fd90 100644 --- a/frontend/src/lib/components/icons/GSheetsIcon.svelte +++ b/frontend/src/lib/components/icons/GSheetsIcon.svelte @@ -1,21 +1,69 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/lib/components/icons/GcalIcon.svelte b/frontend/src/lib/components/icons/GcalIcon.svelte index 6b8f90b086..3a46c28c3e 100644 --- a/frontend/src/lib/components/icons/GcalIcon.svelte +++ b/frontend/src/lib/components/icons/GcalIcon.svelte @@ -1,21 +1,103 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/lib/components/icons/GdocsIcon.svelte b/frontend/src/lib/components/icons/GdocsIcon.svelte index 2e202cc2c8..c25121c2d3 100644 --- a/frontend/src/lib/components/icons/GdocsIcon.svelte +++ b/frontend/src/lib/components/icons/GdocsIcon.svelte @@ -1,14 +1,77 @@ - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/lib/components/icons/GdriveIcon.svelte b/frontend/src/lib/components/icons/GdriveIcon.svelte index eff87a3645..70c5466cc0 100644 --- a/frontend/src/lib/components/icons/GdriveIcon.svelte +++ b/frontend/src/lib/components/icons/GdriveIcon.svelte @@ -1,21 +1,71 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/lib/components/icons/GhostCmsIcon.svelte b/frontend/src/lib/components/icons/GhostCmsIcon.svelte index 018960d9b7..fcebf9f38f 100644 --- a/frontend/src/lib/components/icons/GhostCmsIcon.svelte +++ b/frontend/src/lib/components/icons/GhostCmsIcon.svelte @@ -1,12 +1,22 @@ - - + + + diff --git a/frontend/src/lib/components/icons/GiphyIcon.svelte b/frontend/src/lib/components/icons/GiphyIcon.svelte index 521a358840..fc075eeac5 100644 --- a/frontend/src/lib/components/icons/GiphyIcon.svelte +++ b/frontend/src/lib/components/icons/GiphyIcon.svelte @@ -1,12 +1,17 @@ - - + + + + + + + diff --git a/frontend/src/lib/components/icons/GitBookIcon.svelte b/frontend/src/lib/components/icons/GitBookIcon.svelte index b310c868f7..c3d1c1cf52 100644 --- a/frontend/src/lib/components/icons/GitBookIcon.svelte +++ b/frontend/src/lib/components/icons/GitBookIcon.svelte @@ -1,12 +1,24 @@ - - + + + diff --git a/frontend/src/lib/components/icons/GitIcon.svelte b/frontend/src/lib/components/icons/GitIcon.svelte index 08f7631a91..c6dd709daa 100644 --- a/frontend/src/lib/components/icons/GitIcon.svelte +++ b/frontend/src/lib/components/icons/GitIcon.svelte @@ -1,3 +1,4 @@ + diff --git a/frontend/src/lib/components/icons/GithubIcon.svelte b/frontend/src/lib/components/icons/GithubIcon.svelte index 0b47c0d281..0bb14688cd 100644 --- a/frontend/src/lib/components/icons/GithubIcon.svelte +++ b/frontend/src/lib/components/icons/GithubIcon.svelte @@ -1,9 +1,10 @@ + interface Props { - height?: string; - width?: string; + height?: string + width?: string } - let { height = '24px', width = '24px' }: Props = $props(); + let { height = '24px', width = '24px' }: Props = $props() + + + diff --git a/frontend/src/lib/components/icons/GlobalForestWatchIcon.svelte b/frontend/src/lib/components/icons/GlobalForestWatchIcon.svelte new file mode 100644 index 0000000000..44bcd61411 --- /dev/null +++ b/frontend/src/lib/components/icons/GlobalForestWatchIcon.svelte @@ -0,0 +1,22 @@ + + + + diff --git a/frontend/src/lib/components/icons/GmailIcon.svelte b/frontend/src/lib/components/icons/GmailIcon.svelte index 2a3281adef..cbcad5a889 100644 --- a/frontend/src/lib/components/icons/GmailIcon.svelte +++ b/frontend/src/lib/components/icons/GmailIcon.svelte @@ -1,21 +1,19 @@ - + + + + diff --git a/frontend/src/lib/components/icons/GoogleAiIcon.svelte b/frontend/src/lib/components/icons/GoogleAiIcon.svelte index af0e153f7b..9cae17abd8 100644 --- a/frontend/src/lib/components/icons/GoogleAiIcon.svelte +++ b/frontend/src/lib/components/icons/GoogleAiIcon.svelte @@ -7,19 +7,21 @@ let { height = '24px', width = '24px' }: Props = $props() + - - - + + + + - - + + + + + + + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/lib/components/icons/GoogleCloudIcon.svelte b/frontend/src/lib/components/icons/GoogleCloudIcon.svelte index 5fb4e21797..61455feb1d 100644 --- a/frontend/src/lib/components/icons/GoogleCloudIcon.svelte +++ b/frontend/src/lib/components/icons/GoogleCloudIcon.svelte @@ -1,14 +1,20 @@ + diff --git a/frontend/src/lib/components/icons/GoogleDriveIcon.svelte b/frontend/src/lib/components/icons/GoogleDriveIcon.svelte index 0cccffa372..943f4c2cb0 100644 --- a/frontend/src/lib/components/icons/GoogleDriveIcon.svelte +++ b/frontend/src/lib/components/icons/GoogleDriveIcon.svelte @@ -7,29 +7,68 @@ let { height = '24px', width = '24px' }: Props = $props() - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/lib/components/icons/GoogleFormsIcon.svelte b/frontend/src/lib/components/icons/GoogleFormsIcon.svelte index de25179dc4..3c43dab1f9 100644 --- a/frontend/src/lib/components/icons/GoogleFormsIcon.svelte +++ b/frontend/src/lib/components/icons/GoogleFormsIcon.svelte @@ -1,31 +1,64 @@ + - - - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/lib/components/icons/GoogleIcon.svelte b/frontend/src/lib/components/icons/GoogleIcon.svelte index bd53eb1bab..da75fb7a3b 100644 --- a/frontend/src/lib/components/icons/GoogleIcon.svelte +++ b/frontend/src/lib/components/icons/GoogleIcon.svelte @@ -1,36 +1,37 @@ + - - - - + diff --git a/frontend/src/lib/components/icons/GorgiasIcon.svelte b/frontend/src/lib/components/icons/GorgiasIcon.svelte new file mode 100644 index 0000000000..290ca62806 --- /dev/null +++ b/frontend/src/lib/components/icons/GorgiasIcon.svelte @@ -0,0 +1,23 @@ + + + + + + diff --git a/frontend/src/lib/components/icons/GpgKeyIcon.svelte b/frontend/src/lib/components/icons/GpgKeyIcon.svelte new file mode 100644 index 0000000000..86d11487ae --- /dev/null +++ b/frontend/src/lib/components/icons/GpgKeyIcon.svelte @@ -0,0 +1,30 @@ + + + + + + + + + + + + + diff --git a/frontend/src/lib/components/icons/GraphqlIcon.svelte b/frontend/src/lib/components/icons/GraphqlIcon.svelte index 292bdbad20..245cdc7b37 100644 --- a/frontend/src/lib/components/icons/GraphqlIcon.svelte +++ b/frontend/src/lib/components/icons/GraphqlIcon.svelte @@ -1,13 +1,15 @@ +
-

+ {#snippet titleExtra()} + + {/snippet} +
{#if resourceTypeViewerObj.description} -
- -
+ {/if} {#if resourceTypeViewerObj.isFileset} @@ -1147,9 +1165,14 @@ - - {removeMarkdown(truncate(description ?? '', 30))} - +
+ + {removeMarkdown(truncate(description ?? '', 200))} + +
@@ -1367,19 +1390,31 @@ - - {removeMarkdown(truncate(description ?? '', 200))} - + +
+ + {removeMarkdown(truncate(description ?? '', 200))} + +
- + {#if !canWrite} - - Shared globally - - This resource type is from the 'admins' workspace shared with all - workspaces - - + +
+ + Shared globally + + This resource type is from the 'admins' workspace shared with all + workspaces + + +
{:else if $userStore?.is_admin || $userStore?.is_super_admin}