Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 22 additions & 9 deletions .github/workflows/formbricks-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -269,12 +269,20 @@ jobs:
name: Mark Linear release as complete
runs-on: ubuntu-latest
timeout-minutes: 5
# Published artifacts only, and only jobs that always run for a stable release.
#
# update-helm-app-version opens a follow-up PR against main rather than publishing
# anything for this tag, so gating completion on it left Linear stuck on every stable
# release its appVersion PR job failed.
#
# move-stable-tag is excluded for a subtler reason: its called job is gated on
# `!is_prerelease && make_latest`, so it is legitimately skipped for any stable release
# that is not the latest - every patch on an older line. A skipped dependency skips this
# job too, so depending on it would keep Linear stuck on exactly those releases.
needs:
- docker-build-community
- docker-build-cloud
- helm-chart-release
- move-stable-tag
- update-helm-app-version
if: ${{ !github.event.release.prerelease }}
steps:
- name: Harden the runner
Expand All @@ -287,18 +295,23 @@ jobs:
with:
fetch-depth: 0

# Stamp the in-progress release with the tag version before completing it.
# complete only looks up a release by version, it never assigns one, so a
# versioned sync must run first (Linear's recommended scheduled-pipeline flow).
# A versioned sync targets the release carrying this exact version, or creates it when
# none exists - which is what makes patch releases work, since 5.3.4 never had a
# planned train of its own. complete then looks that exact version up; it never
# assigns one, so the sync has to run first.
#
# VERSION is this release's tag with any v-prefix stripped and validated as SemVer by
# release-docker-github.yml. Using the raw tag_name instead would silently create a
# second, v-prefixed Linear release the first time somebody tags v5.6.0.
- name: Stamp Linear release version
uses: linear/linear-release-action@0353b5fa8c00326913966f00557d68f8f30b8b6b # v0.7.0
uses: linear/linear-release-action@17b8c24f8ceb2b98cabaf1965ff83c55dd596fac # v0.15.1
with:
access_key: ${{ secrets.LINEAR_ACCESS_KEY }}
version: ${{ github.event.release.tag_name }}
version: ${{ needs.docker-build-community.outputs.VERSION }}

- name: Complete Linear release
uses: linear/linear-release-action@0353b5fa8c00326913966f00557d68f8f30b8b6b # v0.7.0
uses: linear/linear-release-action@17b8c24f8ceb2b98cabaf1965ff83c55dd596fac # v0.15.1
with:
access_key: ${{ secrets.LINEAR_ACCESS_KEY }}
command: complete
version: ${{ github.event.release.tag_name }}
version: ${{ needs.docker-build-community.outputs.VERSION }}
72 changes: 72 additions & 0 deletions .github/workflows/linear-release-smoke.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: Linear Release Smoke

# Dry-run canary for the Linear release flow. dry_run makes the action scan commits and call
# read-only Linear APIs, then log the action it would have taken; it never mutates the pipeline.
#
# It exists because the real flow only runs on a published release, so a stale action pin or a
# broken install stayed invisible until release day.
#
# WHAT THIS DOES NOT PROVE. The pinned CLI returns from its complete command before resolving a
# release when dry_run is set, so a green run here says nothing about whether an exact version
# resolves or completes - a nonexistent version passes too. Treat it as "the action installs,
# authenticates and scans", not as validation of the release flow. Exact-version completion is
# only exercised by a real published release.
#
# TRIGGERS ARE DELIBERATELY TRUSTED-ONLY. This job holds a Linear key that can mutate the
# pipeline, so it must never run code from outside main. `pull_request` would run the PR's own
# copy of this file, and `workflow_dispatch` runs the copy on whichever ref the caller picks,
# so either would let a pushed branch drop dry_run or add an exfiltration step. Hence: no
# pull_request trigger, push restricted to main, and the ref check on the job below.
on:
workflow_dispatch:
inputs:
version:
description: "Version to target (blank targets the active started release)"
required: false
type: string
push:
branches:
- main
paths:
- .github/workflows/linear-release.yml
- .github/workflows/formbricks-release.yml
- .github/workflows/linear-release-smoke.yml

permissions:
contents: read

jobs:
linear-release-smoke:
name: Dry-run the Linear release flow
runs-on: ubuntu-latest
timeout-minutes: 5
# Belt to the trigger's braces: a workflow_dispatch runs the file as it exists on the ref
# the caller chose, so the key must not be handed to a branch's copy of these steps.
if: github.ref == 'refs/heads/main'
steps:
- name: Harden the runner
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
with:
egress-policy: audit

- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0

- name: Dry-run the release sync
uses: linear/linear-release-action@17b8c24f8ceb2b98cabaf1965ff83c55dd596fac # v0.15.1
with:
access_key: ${{ secrets.LINEAR_ACCESS_KEY }}
version: ${{ inputs.version }}
dry_run: "true"
log_level: verbose

- name: Dry-run the release completion
uses: linear/linear-release-action@17b8c24f8ceb2b98cabaf1965ff83c55dd596fac # v0.15.1
with:
access_key: ${{ secrets.LINEAR_ACCESS_KEY }}
command: complete
version: ${{ inputs.version }}
dry_run: "true"
log_level: verbose
2 changes: 1 addition & 1 deletion .github/workflows/linear-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ jobs:
fetch-depth: 0

- name: Sync Linear release
uses: linear/linear-release-action@0353b5fa8c00326913966f00557d68f8f30b8b6b # v0.7.0
uses: linear/linear-release-action@17b8c24f8ceb2b98cabaf1965ff83c55dd596fac # v0.15.1
with:
access_key: ${{ secrets.LINEAR_ACCESS_KEY }}
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,25 @@
isFormbricksSurveysConfigured: boolean;
}

/**
* A nav section header carrying a Beta badge.
*
* Analyze and Act are both pre-1.0 surfaces, and the badge is what tells someone the difference
* between "this is finished" and "this is early". Extracted rather than duplicated so the two
* sections cannot drift into looking subtly different from each other.
*/
const sectionLabelWithBeta = (label: React.ReactNode) => (
<span className="inline-flex items-center gap-2">
<span>{label}</span>
<Badge
text="Beta"
type="gray"
size="tiny"
className="text-[10px] font-semibold tracking-normal normal-case"
/>
</span>
);

export const MainNavigation = ({
organization,
user,
Expand Down Expand Up @@ -151,18 +170,9 @@
},
{
id: "unify-feedback",
name: (
<span className="inline-flex items-center gap-2">
{/* Product section (IA) label — intentionally not localized (kept in English across all locales) */}
<span>Unify</span>
<Badge
text="Beta"
type="gray"
size="tiny"
className="text-[10px] font-semibold tracking-normal normal-case"
/>
</span>
),
// Same policy as "Ask" above: product section labels stay English in every locale.
// Was "Unify" until ENG-2742 settled on Ask / Analyze / Act as the three pillars.
name: sectionLabelWithBeta("Analyze"),
items: [
{
name: t("workspace.unify.feedback_data"),
Expand All @@ -184,7 +194,11 @@
},
{
id: "act",
name: t("common.act"),
// Kept translated, unlike "Ask" and "Analyze" above. Those two are deliberately English in
// every locale; this one has been going through t() since it was added. Making the three
// consistent means dropping a string 15 locales already translate, which is a naming
// decision rather than a side effect of adding a badge — see ENG-2742.
name: sectionLabelWithBeta(t("common.act")),
items: [
{
name: t("common.workflows"),
Expand Down Expand Up @@ -353,7 +367,7 @@
}
});
},
[router, organization.id, workspace.id]

Check warning on line 370 in apps/web/app/(app)/workspaces/[workspaceId]/components/MainNavigation.tsx

View workflow job for this annotation

GitHub Actions / Run Linters / Linters

React Hook useCallback has an unnecessary dependency: 'workspace.id'. Either exclude it or remove the dependency array
);

const switcherTriggerClasses = cn(
Expand Down
2 changes: 2 additions & 0 deletions apps/web/app/api/v1/management/responses/lib/response.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ vi.mock("@/lib/constants", () => ({
STRIPE_API_VERSION: "2026-01-28.clover",
IS_PRODUCTION: false,
SENTRY_DSN: "mock-sentry-dsn",
COMMUNITY_WORKSPACE_LIMIT: 1,
CLOUD_HOBBY_WORKSPACE_LIMIT: 1,
}));
vi.mock("@/lib/utils/helper");
vi.mock("@/lib/response/service");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ vi.mock("@/lib/constants", () => ({
SMTP_HOST: "mock-smtp-host",
SMTP_PORT: "mock-smtp-port",
STRIPE_API_VERSION: "2026-01-28.clover",
COMMUNITY_WORKSPACE_LIMIT: 1,
CLOUD_HOBBY_WORKSPACE_LIMIT: 1,
}));

vi.mock("@/lib/organization/service");
Expand Down
2 changes: 1 addition & 1 deletion apps/web/i18n.lock
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,7 @@ checksums:
common/yes: ec580fd11a45779b039466f1e35eed2a
common/you_are_downgraded_to_the_community_edition: e3ae56502ff787109cae0997519f628e
common/you_are_not_authorized_to_perform_this_action: 1b3255ab740582ddff016a399f8bf302
common/you_have_reached_your_limit_of_workspace_limit: 506a6ee315d9754da7ea26929bc40f52
common/you_have_reached_your_limit_of_workspace_limit: 444293496a4029ea608ec12266bb2e94
common/you_have_reached_your_monthly_response_limit_of_count: d1e427dbc5ce704bd6b9e188d8407e07
common/you_will_be_downgraded_to_the_community_edition_on_date: bff35b54c13e2c205dc4c19056261cc0
common/your_license_has_expired_please_renew: 3f21ae4a7deab351b143b407ece58254
Expand Down
15 changes: 15 additions & 0 deletions apps/web/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,21 @@ export const TEXT_RESPONSES_PER_PAGE = 5;
export const MAX_RESPONSES_FOR_INSIGHT_GENERATION = 500;
export const MAX_OTHER_OPTION_LENGTH = 250;

/**
* Workspaces an organization gets on a self-hosted instance with no active enterprise license
* (Community Edition). Mirrors docs/self-hosting/advanced/license.mdx.
*/
export const COMMUNITY_WORKSPACE_LIMIT = 1;

/**
* Workspaces a cloud organization falls back to when the license server cannot confirm the instance
* license (expired, invalid_license, instance_mismatch, unreachable). Deliberately the Hobby (free
* tier) allowance: an entitlement we cannot verify is treated as no entitlement. The create gate is
* `count >= limit`, so an org already above it keeps every workspace it has and only pauses creating
* new ones until the license resolves.
*/
export const CLOUD_HOBBY_WORKSPACE_LIMIT = 1;

export const SKIP_INVITE_FOR_SSO = env.AUTH_SKIP_INVITE_FOR_SSO === "1";
export const DEFAULT_TEAM_ID = env.AUTH_DEFAULT_TEAM_ID;

Expand Down
2 changes: 1 addition & 1 deletion apps/web/locales/de-DE.json
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@
"yes": "Ja",
"you_are_downgraded_to_the_community_edition": "Du wurdest auf die Community Edition herabgestuft.",
"you_are_not_authorized_to_perform_this_action": "Du bist nicht berechtigt, diese Aktion durchzuführen.",
"you_have_reached_your_limit_of_workspace_limit": "Du hast dein Limit von {workspaceLimit} Workspaces erreicht.",
"you_have_reached_your_limit_of_workspace_limit": "Du hast dein Workspace-Limit ({workspaceLimit}) erreicht.",
"you_have_reached_your_monthly_response_limit_of_count": "Du hast dein monatliches Antwortlimit von {count} erreicht.",
"you_will_be_downgraded_to_the_community_edition_on_date": "Du wirst am {date} auf die Community Edition herabgestuft.",
"your_license_has_expired_please_renew": "Deine Enterprise-Lizenz ist abgelaufen. Bitte erneuere sie, um weiterhin Enterprise-Funktionen nutzen zu können.",
Expand Down
2 changes: 1 addition & 1 deletion apps/web/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@
"yes": "Yes",
"you_are_downgraded_to_the_community_edition": "You are downgraded to the Community Edition.",
"you_are_not_authorized_to_perform_this_action": "You are not authorized to perform this action.",
"you_have_reached_your_limit_of_workspace_limit": "You have reached your limit of {workspaceLimit} workspaces.",
"you_have_reached_your_limit_of_workspace_limit": "You have reached your workspace limit ({workspaceLimit}).",
"you_have_reached_your_monthly_response_limit_of_count": "You have reached your monthly response limit of {count}.",
"you_will_be_downgraded_to_the_community_edition_on_date": "You will be downgraded to the Community Edition on {date}.",
"your_license_has_expired_please_renew": "Your enterprise license has expired. Please renew it to continue using enterprise features.",
Expand Down
2 changes: 1 addition & 1 deletion apps/web/locales/es-ES.json
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@
"yes": "Sí",
"you_are_downgraded_to_the_community_edition": "Has sido degradado a la edición Community.",
"you_are_not_authorized_to_perform_this_action": "No tienes autorización para realizar esta acción.",
"you_have_reached_your_limit_of_workspace_limit": "Has alcanzado tu límite de {workspaceLimit} espacios de trabajo.",
"you_have_reached_your_limit_of_workspace_limit": "Has alcanzado el límite de tu espacio de trabajo ({workspaceLimit}).",
"you_have_reached_your_monthly_response_limit_of_count": "Has alcanzado tu límite mensual de respuestas de {count}.",
"you_will_be_downgraded_to_the_community_edition_on_date": "Serás degradado a la edición Community el {date}.",
"your_license_has_expired_please_renew": "Your enterprise license has expired. Please renew it to continue using enterprise features.",
Expand Down
2 changes: 1 addition & 1 deletion apps/web/locales/fr-FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@
"yes": "Oui",
"you_are_downgraded_to_the_community_edition": "Vous êtes rétrogradé à l'édition communautaire.",
"you_are_not_authorized_to_perform_this_action": "Vous n'êtes pas autorisé à effectuer cette action.",
"you_have_reached_your_limit_of_workspace_limit": "Vous avez atteint votre limite de {workspaceLimit} espaces de travail.",
"you_have_reached_your_limit_of_workspace_limit": "Tu as atteint la limite de ton espace de travail ({workspaceLimit}).",
"you_have_reached_your_monthly_response_limit_of_count": "Vous avez atteint votre limite mensuelle de {count} réponses.",
"you_will_be_downgraded_to_the_community_edition_on_date": "Vous serez rétrogradé à l'édition communautaire le {date}.",
"your_license_has_expired_please_renew": "Your enterprise license has expired. Please renew it to continue using enterprise features.",
Expand Down
2 changes: 1 addition & 1 deletion apps/web/locales/hu-HU.json
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@
"yes": "Igen",
"you_are_downgraded_to_the_community_edition": "Visszaváltott a közösségi kiadásra.",
"you_are_not_authorized_to_perform_this_action": "Nincs felhatalmazva ennek a műveletnek a végrehajtásához.",
"you_have_reached_your_limit_of_workspace_limit": "Elérte a(z) {workspaceLimit} munkaterületből álló korlátját.",
"you_have_reached_your_limit_of_workspace_limit": "Elérte a munkaterület korlátját ({workspaceLimit}).",
"you_have_reached_your_monthly_response_limit_of_count": "Elérte a havi {count} értékű válaszkorlátját.",
"you_will_be_downgraded_to_the_community_edition_on_date": "Vissza lesz állítva a közösségi kiadásra ekkor: {date}.",
"your_license_has_expired_please_renew": "A vállalati licence lejárt. Újítsa meg, hogy továbbra is használhassa a vállalati funkciókat.",
Expand Down
2 changes: 1 addition & 1 deletion apps/web/locales/ja-JP.json
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@
"yes": "はい",
"you_are_downgraded_to_the_community_edition": "コミュニティ版にダウングレードされました。",
"you_are_not_authorized_to_perform_this_action": "このアクションを実行する権限がありません。",
"you_have_reached_your_limit_of_workspace_limit": "ワークスペースの上限である{workspaceLimit}件に達しました。",
"you_have_reached_your_limit_of_workspace_limit": "ワークスペースの上限({workspaceLimit})に達しました。",
"you_have_reached_your_monthly_response_limit_of_count": "月間の回答制限{count}件に達しました。",
"you_will_be_downgraded_to_the_community_edition_on_date": "コミュニティ版へのダウングレードは {date} に行われます。",
"your_license_has_expired_please_renew": "Your enterprise license has expired. Please renew it to continue using enterprise features.",
Expand Down
2 changes: 1 addition & 1 deletion apps/web/locales/nl-NL.json
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@
"yes": "Ja",
"you_are_downgraded_to_the_community_edition": "Je bent gedowngraded naar de Community-editie.",
"you_are_not_authorized_to_perform_this_action": "U bent niet geautoriseerd om deze actie uit te voeren.",
"you_have_reached_your_limit_of_workspace_limit": "Je hebt je limiet van {workspaceLimit} workspaces bereikt.",
"you_have_reached_your_limit_of_workspace_limit": "Je hebt je limiet van werkruimtes bereikt ({workspaceLimit}).",
"you_have_reached_your_monthly_response_limit_of_count": "Je hebt je maandelijkse responslimiet van {count} bereikt.",
"you_will_be_downgraded_to_the_community_edition_on_date": "Je wordt gedowngraded naar de Community-editie op {date}.",
"your_license_has_expired_please_renew": "Your enterprise license has expired. Please renew it to continue using enterprise features.",
Expand Down
2 changes: 1 addition & 1 deletion apps/web/locales/pt-BR.json
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@
"yes": "Sim",
"you_are_downgraded_to_the_community_edition": "Você foi rebaixado para a Edição Comunitária.",
"you_are_not_authorized_to_perform_this_action": "Você não tem autorização para realizar essa ação.",
"you_have_reached_your_limit_of_workspace_limit": "Você atingiu o limite de {workspaceLimit} espaços de trabalho.",
"you_have_reached_your_limit_of_workspace_limit": "Você atingiu o limite do seu workspace ({workspaceLimit}).",
"you_have_reached_your_monthly_response_limit_of_count": "Você atingiu seu limite mensal de respostas de {count}.",
"you_will_be_downgraded_to_the_community_edition_on_date": "Você será rebaixado para a Edição Comunitária em {date}.",
"your_license_has_expired_please_renew": "Your enterprise license has expired. Please renew it to continue using enterprise features.",
Expand Down
2 changes: 1 addition & 1 deletion apps/web/locales/pt-PT.json
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@
"yes": "Sim",
"you_are_downgraded_to_the_community_edition": "Foi rebaixado para a Edição Comunitária.",
"you_are_not_authorized_to_perform_this_action": "Não está autorizado a realizar esta ação.",
"you_have_reached_your_limit_of_workspace_limit": "Atingiste o teu limite de {workspaceLimit} espaços de trabalho.",
"you_have_reached_your_limit_of_workspace_limit": "Atingiste o teu limite de espaço de trabalho ({workspaceLimit}).",
"you_have_reached_your_monthly_response_limit_of_count": "Atingiste o teu limite mensal de respostas de {count}.",
"you_will_be_downgraded_to_the_community_edition_on_date": "Será rebaixado para a Edição Comunitária em {date}.",
"your_license_has_expired_please_renew": "Your enterprise license has expired. Please renew it to continue using enterprise features.",
Expand Down
2 changes: 1 addition & 1 deletion apps/web/locales/ro-RO.json
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@
"yes": "Da",
"you_are_downgraded_to_the_community_edition": "Ai fost retrogradat la ediția Community.",
"you_are_not_authorized_to_perform_this_action": "Nu sunteți autorizat să efectuați această acțiune.",
"you_have_reached_your_limit_of_workspace_limit": "Ai atins limita de {workspaceLimit} spații de lucru.",
"you_have_reached_your_limit_of_workspace_limit": "Ai atins limita spațiului de lucru ({workspaceLimit}).",
"you_have_reached_your_monthly_response_limit_of_count": "Ai atins limita lunară de răspunsuri de {count}.",
"you_will_be_downgraded_to_the_community_edition_on_date": "Vei fi retrogradat la ediția Community pe {date}.",
"your_license_has_expired_please_renew": "Your enterprise license has expired. Please renew it to continue using enterprise features.",
Expand Down
Loading
Loading