Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
9800d02
feat(auth): add Lark/Feishu OAuth provider
May 12, 2026
b4edb02
feat(auth/web): add "Sign in with Lark" button to web + space frontends
May 12, 2026
bf75c60
fix(auth/lark): unblock callback by removing LogRecord reserved-key c…
May 12, 2026
5bc9739
ci(docker): expose pnpm global bin so turbo install succeeds
May 13, 2026
939e994
feat(auth/lark): synthesize stable identifier when directory exposes …
May 13, 2026
89b1c9b
feat(workspace/lark): add Lark directory contacts + batch invite endp…
May 13, 2026
cafb783
feat(web/workspace): Invite-from-Lark modal in members settings
May 13, 2026
03ce387
perf(lark-contacts): concurrent dept crawl + 10-min Redis cache
May 13, 2026
57220d6
feat(i18n): VITE_DEFAULT_LANGUAGE env + browser auto-detect
May 13, 2026
e6cd4a1
i18n(zh-CN): rename 工作项 to 任务
May 13, 2026
aa060ac
feat(auth/lark): auto-join SSO users into a default workspace
May 13, 2026
255f23a
feat(lark): hourly directory auto-sync + manual trigger endpoint
May 13, 2026
e7f41b7
feat(web/workspace): Sync from Lark button next to Invite from Lark
May 13, 2026
9f808ec
fix(auth/lark): set display_name from Feishu name on user creation
May 13, 2026
243d63d
feat(lark): auto-add workspace members to new projects via signal
May 13, 2026
3ef078d
feat(lark): Feishu Bot DM notifications for issue events
May 13, 2026
18cd413
fix(lark): dispatch bot notifications from bulk_create, not post_save
May 14, 2026
db987e8
fix(lark): only autojoin workspace members into Public projects
May 14, 2026
3408412
chore(branding): rebrand user-visible "Plane" -> "Tick"; remove GitHu…
May 14, 2026
955f3bf
chore(branding): rebrand SPA entry-point metadata (root.tsx, constants)
May 14, 2026
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
7 changes: 6 additions & 1 deletion apps/admin/Dockerfile.admin
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ WORKDIR /app

ENV TURBO_TELEMETRY_DISABLED=1
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
ENV PATH="$PNPM_HOME:$PNPM_HOME/bin:$PATH"
ENV CI=1

RUN corepack enable pnpm
Expand Down Expand Up @@ -53,6 +53,11 @@ ENV VITE_WEB_BASE_URL=$VITE_WEB_BASE_URL
ARG VITE_WEB_BASE_PATH=""
ENV VITE_WEB_BASE_PATH=$VITE_WEB_BASE_PATH

# Optional build-time language pin for self-hosted deploys. Empty string falls
# through to navigator.language detection at runtime.
ARG VITE_DEFAULT_LANGUAGE=""
ENV VITE_DEFAULT_LANGUAGE=$VITE_DEFAULT_LANGUAGE

ARG VITE_WEBSITE_URL="https://plane.so"
ENV VITE_WEBSITE_URL=$VITE_WEBSITE_URL
ARG VITE_SUPPORT_EMAIL="support@plane.so"
Expand Down
2 changes: 1 addition & 1 deletion apps/admin/app/(all)/(dashboard)/ai/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const InstanceAIPage = observer(function InstanceAIPage(_props: Route.ComponentP
<PageWrapper
header={{
title: "AI features for all your workspaces",
description: "Configure your AI API credentials so Plane AI features are turned on for all your workspaces.",
description: "Configure your AI API credentials so Tick AI features are turned on for all your workspaces.",
}}
>
{formattedConfig ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ export function InstanceGitlabConfigForm(props: Props) {
<div className="flex flex-col gap-8">
<div className="grid w-full grid-cols-2 gap-x-12 gap-y-8">
<div className="col-span-2 flex flex-col gap-y-4 pt-1 md:col-span-1">
<div className="pt-2.5 text-18 font-medium">GitLab-provided details for Plane</div>
<div className="pt-2.5 text-18 font-medium">GitLab-provided details for Tick</div>
{GITLAB_FORM_FIELDS.map((field) => (
<ControllerInput
key={field.key}
Expand Down
2 changes: 1 addition & 1 deletion apps/admin/app/(all)/(dashboard)/authentication/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,6 @@ const InstanceAuthenticationPage = observer(function InstanceAuthenticationPage(
);
});

export const meta: Route.MetaFunction = () => [{ title: "Authentication Settings - Plane Web" }];
export const meta: Route.MetaFunction = () => [{ title: "Authentication Settings - Tick" }];

export default InstanceAuthenticationPage;
4 changes: 2 additions & 2 deletions apps/admin/app/(all)/(dashboard)/general/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,9 @@ export const GeneralConfigurationForm = observer(function GeneralConfigurationFo
</div>
</div>
<div className="grow">
<div className="text-13 leading-5 font-medium text-primary">Let Plane collect anonymous usage data</div>
<div className="text-13 leading-5 font-medium text-primary">Let Tick collect anonymous usage data</div>
<div className="text-11 leading-5 font-regular text-tertiary">
No PII is collected.This anonymized data is used to understand how you use Plane and build new features
No PII is collected.This anonymized data is used to understand how you use Tick and build new features
in line with{" "}
<a
href="https://developers.plane.so/self-hosting/telemetry"
Expand Down
4 changes: 2 additions & 2 deletions apps/admin/app/(all)/(dashboard)/sidebar-help-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,13 @@ export const AdminSidebarHelpSection = observer(function AdminSidebarHelpSection
)}
>
<div className={`flex items-center gap-1 ${isSidebarCollapsed ? "flex-col justify-center" : "w-full"}`}>
<Tooltip tooltipContent="Redirect to Plane" position="right" className="ml-4" disabled={!isSidebarCollapsed}>
<Tooltip tooltipContent="Redirect to Tick" position="right" className="ml-4" disabled={!isSidebarCollapsed}>
<a
href={redirectionLink}
className={`relative flex items-center gap-1 rounded-sm bg-layer-1 px-2 py-1 text-body-xs-medium whitespace-nowrap text-secondary`}
>
<NewTabIcon width={14} height={14} />
{!isSidebarCollapsed && "Redirect to Plane"}
{!isSidebarCollapsed && "Redirect to Tick"}
</a>
</Tooltip>
<Tooltip tooltipContent="Help" position={isSidebarCollapsed ? "right" : "top"} className="ml-4">
Expand Down
2 changes: 1 addition & 1 deletion apps/admin/app/(all)/(home)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,5 @@ export default observer(HomePage);

export const meta: Route.MetaFunction = () => [
{ title: "Admin – Instance Setup & Sign-In" },
{ name: "description", content: "Configure your Plane instance or sign in to the admin portal." },
{ name: "description", content: "Configure your Tick instance or sign in to the admin portal." },
];
2 changes: 1 addition & 1 deletion apps/admin/app/(all)/(home)/sign-in-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export function InstanceSignInForm() {
<div className="mt-10 flex w-full flex-grow flex-col items-center justify-center py-6">
<div className="relative flex w-full max-w-[22.5rem] flex-col gap-6">
<FormHeader
heading="Manage your Plane instance"
heading="Manage your Tick instance"
subHeading="Configure instance-wide settings to secure your instance"
/>
<form
Expand Down
4 changes: 4 additions & 0 deletions apps/admin/app/assets/logos/lark-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion apps/admin/app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import interVariableWoff2 from "@fontsource-variable/inter/files/inter-latin-wgh
import "@fontsource/material-symbols-rounded";
import "@fontsource/ibm-plex-mono";

const APP_TITLE = "Plane | Simple, extensible, open-source project management tool.";
const APP_TITLE = "Tick · 任务管理";
const APP_DESCRIPTION =
"Open-source project management tool to manage work items, sprints, and product roadmaps with peace of mind.";

Expand Down
57 changes: 57 additions & 0 deletions apps/admin/components/authentication/lark-config.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/

import { observer } from "mobx-react";
import Link from "next/link";
// icons
import { Settings2 } from "lucide-react";
// plane internal packages
import { getButtonStyling } from "@plane/propel/button";
import type { TInstanceAuthenticationMethodKeys } from "@plane/types";
import { ToggleSwitch } from "@plane/ui";
import { cn } from "@plane/utils";
// hooks
import { useInstance } from "@/hooks/store";

type Props = {
disabled: boolean;
updateConfig: (key: TInstanceAuthenticationMethodKeys, value: string) => void;
};

export const LarkConfiguration = observer(function LarkConfiguration(props: Props) {
const { disabled, updateConfig } = props;
// store
const { formattedConfig } = useInstance();
// derived values
const enableLarkConfig = formattedConfig?.IS_LARK_ENABLED ?? "";
const isLarkConfigured = !!formattedConfig?.LARK_CLIENT_ID && !!formattedConfig?.LARK_CLIENT_SECRET;

return (
<>
{isLarkConfigured ? (
<div className="flex items-center gap-4">
<Link href="/authentication/lark" className={cn(getButtonStyling("link", "base"), "font-medium")}>
Edit
</Link>
<ToggleSwitch
value={Boolean(parseInt(enableLarkConfig))}
onChange={() => {
const newEnableLarkConfig = Boolean(parseInt(enableLarkConfig)) === true ? "0" : "1";
updateConfig("IS_LARK_ENABLED", newEnableLarkConfig);
}}
size="sm"
disabled={disabled}
/>
</div>
) : (
<Link href="/authentication/lark" className={cn(getButtonStyling("secondary", "base"), "text-tertiary")}>
<Settings2 className="h-4 w-4 p-0.5 text-tertiary" />
Configure
</Link>
)}
</>
);
});
10 changes: 10 additions & 0 deletions apps/admin/hooks/oauth/core.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ import githubLightModeImage from "@/app/assets/logos/github-black.png?url";
import githubDarkModeImage from "@/app/assets/logos/github-white.png?url";
import gitlabLogo from "@/app/assets/logos/gitlab-logo.svg?url";
import googleLogo from "@/app/assets/logos/google-logo.svg?url";
import larkLogo from "@/app/assets/logos/lark-logo.svg?url";
// components
import { EmailCodesConfiguration } from "@/components/authentication/email-config-switch";
import { GiteaConfiguration } from "@/components/authentication/gitea-config";
import { GithubConfiguration } from "@/components/authentication/github-config";
import { GitlabConfiguration } from "@/components/authentication/gitlab-config";
import { GoogleConfiguration } from "@/components/authentication/google-config";
import { LarkConfiguration } from "@/components/authentication/lark-config";
import { PasswordLoginConfiguration } from "@/components/authentication/password-config-switch";

// Authentication methods
Expand Down Expand Up @@ -89,4 +91,12 @@ export const getCoreAuthenticationModesMap: (
config: <GiteaConfiguration disabled={disabled} updateConfig={updateConfig} />,
enabledConfigKey: "IS_GITEA_ENABLED",
},
lark: {
key: "lark",
name: "Lark / Feishu",
description: "Allow members to log in or sign up for Plane with their Lark or Feishu accounts.",
icon: <img src={larkLogo} height={20} width={20} alt="Lark Logo" />,
config: <LarkConfiguration disabled={disabled} updateConfig={updateConfig} />,
enabledConfigKey: "IS_LARK_ENABLED",
},
});
18 changes: 18 additions & 0 deletions apps/api/plane/app/urls/workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@
WorkspaceHomePreferenceViewSet,
WorkspaceStickyViewSet,
WorkspaceUserPreferenceViewSet,
LarkContactsListEndpoint,
LarkSyncTriggerEndpoint,
LarkWorkspaceInviteEndpoint,
)


Expand Down Expand Up @@ -67,6 +70,21 @@
WorkspaceInvitationsViewset.as_view({"get": "list", "post": "create"}),
name="workspace-invitations",
),
path(
"workspaces/<str:slug>/lark-contacts/",
LarkContactsListEndpoint.as_view(),
name="workspace-lark-contacts",
),
path(
"workspaces/<str:slug>/lark-invite/",
LarkWorkspaceInviteEndpoint.as_view(),
name="workspace-lark-invite",
),
path(
"workspaces/<str:slug>/lark-sync/",
LarkSyncTriggerEndpoint.as_view(),
name="workspace-lark-sync",
),
path(
"workspaces/<str:slug>/invitations/<uuid:pk>/",
WorkspaceInvitationsViewset.as_view({"delete": "destroy", "get": "retrieve", "patch": "partial_update"}),
Expand Down
5 changes: 5 additions & 0 deletions apps/api/plane/app/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@
WorkspaceJoinEndpoint,
UserWorkspaceInvitationsViewSet,
)
from .workspace.lark_invite import (
LarkContactsListEndpoint,
LarkSyncTriggerEndpoint,
LarkWorkspaceInviteEndpoint,
)
from .workspace.label import WorkspaceLabelsEndpoint
from .workspace.state import WorkspaceStatesEndpoint
from .workspace.user import (
Expand Down
Loading