Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
070d776
[fa] chore: add project rules and changes tracker
dadenegarco Feb 23, 2026
9280c2c
[fa] feat: implement shamsi date picker component
dadenegarco Feb 23, 2026
22ff33c
[fa] feat: add Jalali calendar support for gantt chart and calendar view
dadenegarco Feb 25, 2026
580c666
Merge pull request #1 from dadenegarco/fa/chore/add-rules-and-tracker
dadenegarco Feb 25, 2026
599abd8
[fa] fix: switch shamsi date picker to persian_en locale
dadenegarco Feb 25, 2026
832d106
[fa] fix: improve RTL spacing in rich text editor
dadenegarco Feb 25, 2026
351216e
[fa] feat: add Yekan Bakh font, auto bidi text direction, and Jalali …
dadenegarco Feb 25, 2026
7ba5496
Merge pull request #2 from dadenegarco/fa/feat/yekan-bakh-bidi-rtl
dadenegarco Feb 25, 2026
56276e6
ci: add GitHub Actions deploy pipeline with GHCR
dadenegarco Feb 25, 2026
da59215
Merge pull request #4 from dadenegarco/ci/deploy-pipeline
dadenegarco Feb 25, 2026
dad19a3
[fa] feat: add CSV importer, CI/CD pipeline, and UI improvements
dadenegarco Feb 25, 2026
a43c22a
Merge pull request #5 from dadenegarco/fa/feat/yekan-bakh-bidi-rtl
dadenegarco Feb 25, 2026
293e971
[fa] fix: fix 3 bugs in Shamsi/Jalali calendar implementation
dadenegarco Feb 26, 2026
204971e
[fa] feat: set jalali as default calendar system for new users
dadenegarco Feb 26, 2026
f93fd28
[fa] fix: deduplicate assignee names and include workspace members in…
dadenegarco Feb 28, 2026
5fe9604
[fa] feat: add configurable LLM base URL to AI settings
dadenegarco Mar 5, 2026
bf9bcc2
[fa] chore: ignore Claude Code local settings from git
dadenegarco Mar 5, 2026
2125367
[fa] fix: strip bracket notation from assignee/status values during i…
dadenegarco Mar 6, 2026
488fa25
[fa] feat: align import feature with project design system and enable…
dadenegarco Mar 8, 2026
81d7dad
[fa] fix: use correct theme variables in Shamsi calendar for dark mod…
dadenegarco Mar 8, 2026
c521354
[fa] refactor: centralise CSV cell sanitisation with _clean_cell help…
dadenegarco Mar 8, 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
36 changes: 36 additions & 0 deletions .cursorrules
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
# Plane-FA Cursor Rules

## Core Rule: Upstream Safety
- Never modify core Plane files without strong reason
- If you must modify a core file, add comment: // [FA-CUSTOM] reason
- Prefer: create new file and extend, don't modify original
- Prefer: wrapper component over modifying original component

## FA File Structure
- lib/fa/ → FA utility functions
- components/fa/ → FA components
- locales/fa/ → translations
- hooks/fa/ → FA hooks

## Naming Convention
- Files: fa-date-utils.ts
- Components: FaDatePicker.tsx
- Env vars: FA_ENABLE_SHAMSI

## TypeScript
- No `any` — always explicit types
- Date objects not raw strings
- Keep interfaces in types/fa/

## Git Commit Format
[fa] feat: add shamsi date picker
[fa] fix: rtl layout in sidebar
[fa] chore: sync with upstream

## Never
- ❌ secrets in code
- ❌ console.log in production code
- ❌ commit directly to main
- ❌ commit .env file
---
210 changes: 210 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
name: Build & Deploy to Production

on:
push:
branches:
- develop

concurrency:
group: deploy-production
cancel-in-progress: false # don't cancel mid-deploy

env:
REGISTRY: ghcr.io
IMAGE_PREFIX: ghcr.io/${{ github.repository }}

jobs:
# ---------------------------------------------------------------------------
# Build all Docker images in parallel and push to GHCR
# ---------------------------------------------------------------------------
build-web:
name: Build Web (plane-frontend)
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v6
with:
context: .
file: ./apps/web/Dockerfile.web
push: true
tags: |
${{ env.IMAGE_PREFIX }}/plane-frontend:latest
${{ env.IMAGE_PREFIX }}/plane-frontend:${{ github.sha }}
cache-from: type=gha,scope=web
cache-to: type=gha,mode=max,scope=web

build-admin:
name: Build Admin (plane-admin)
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v6
with:
context: .
file: ./apps/admin/Dockerfile.admin
push: true
tags: |
${{ env.IMAGE_PREFIX }}/plane-admin:latest
${{ env.IMAGE_PREFIX }}/plane-admin:${{ github.sha }}
cache-from: type=gha,scope=admin
cache-to: type=gha,mode=max,scope=admin

build-space:
name: Build Space (plane-space)
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v6
with:
context: .
file: ./apps/space/Dockerfile.space
push: true
tags: |
${{ env.IMAGE_PREFIX }}/plane-space:latest
${{ env.IMAGE_PREFIX }}/plane-space:${{ github.sha }}
cache-from: type=gha,scope=space
cache-to: type=gha,mode=max,scope=space

build-live:
name: Build Live (plane-live)
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v6
with:
context: .
file: ./apps/live/Dockerfile.live
push: true
tags: |
${{ env.IMAGE_PREFIX }}/plane-live:latest
${{ env.IMAGE_PREFIX }}/plane-live:${{ github.sha }}
cache-from: type=gha,scope=live
cache-to: type=gha,mode=max,scope=live

build-api:
name: Build API (plane-backend)
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v6
with:
context: ./apps/api
file: ./apps/api/Dockerfile.api
push: true
tags: |
${{ env.IMAGE_PREFIX }}/plane-backend:latest
${{ env.IMAGE_PREFIX }}/plane-backend:${{ github.sha }}
cache-from: type=gha,scope=api
cache-to: type=gha,mode=max,scope=api

build-proxy:
name: Build Proxy (plane-proxy)
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v6
with:
context: ./apps/proxy
file: ./apps/proxy/Dockerfile.ce
push: true
tags: |
${{ env.IMAGE_PREFIX }}/plane-proxy:latest
${{ env.IMAGE_PREFIX }}/plane-proxy:${{ github.sha }}
cache-from: type=gha,scope=proxy
cache-to: type=gha,mode=max,scope=proxy

# ---------------------------------------------------------------------------
# Deploy to production server via SSH
# ---------------------------------------------------------------------------
deploy:
name: Deploy to Server
runs-on: ubuntu-latest
needs:
- build-web
- build-admin
- build-space
- build-live
- build-api
- build-proxy
steps:
- name: Deploy via SSH
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script_stop: true
script: |
set -e
echo "=== Deploy started at $(date) ==="

# Login to GHCR
echo "${{ secrets.GHCR_PAT }}" | docker login ghcr.io -u "${{ secrets.GHCR_USER }}" --password-stdin

cd /root/apps/plane-fa/deploy

# Pull latest images
docker compose pull

# Rolling restart (db/redis/mq stay untouched)
docker compose up -d --remove-orphans

# Cleanup dangling images
docker image prune -f

echo "=== Deploy finished at $(date) ==="
docker compose ps
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
node_modules
.next
.yarn
.claude/settings.local.json

### NextJS ###
# Dependencies
Expand Down
26 changes: 26 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# CHANGES.md — FA Customizations vs Upstream

## Last Upstream Sync
- Date: ---
- Upstream commit: ---

## Core Files Modified (⚠️ watch on sync)
| File | Change | Conflict Risk |
|------|--------|---------------|
| - | - | - |

## New Files Added (low risk)
| File | Purpose |
|------|---------|
| lib/fa/date-utils.ts | Shamsi calendar utils |
| lib/fa/features.ts | Feature flags |
| locales/fa/fa.json | Persian translations |

## How To Sync Upstream
```bash
git fetch upstream
git log HEAD..upstream/main --oneline # see what's new
git diff HEAD..upstream/main --name-only # see changed files
git rebase upstream/main # apply upstream changes
# resolve conflicts if any
```
14 changes: 12 additions & 2 deletions apps/admin/app/(all)/(dashboard)/ai/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,20 @@ export function InstanceAIForm(props: IInstanceAIForm) {
defaultValues: {
LLM_API_KEY: config["LLM_API_KEY"],
LLM_MODEL: config["LLM_MODEL"],
LLM_BASE_URL: config["LLM_BASE_URL"],
},
});

const aiFormFields: TControllerInputFormField[] = [
{
key: "LLM_BASE_URL",
type: "text",
label: "Base URL",
description: "The base URL for your LLM API. Leave empty for default OpenAI endpoint.",
placeholder: "https://api.openai.com/v1",
error: Boolean(errors.LLM_BASE_URL),
required: false,
},
{
key: "LLM_MODEL",
type: "text",
Expand Down Expand Up @@ -100,8 +110,8 @@ export function InstanceAIForm(props: IInstanceAIForm) {
<div className="space-y-8">
<div className="space-y-3">
<div>
<div className="pb-1 text-18 font-medium text-primary">OpenAI</div>
<div className="text-13 font-regular text-tertiary">If you use ChatGPT, this is for you.</div>
<div className="pb-1 text-18 font-medium text-primary">AI / LLM</div>
<div className="text-13 font-regular text-tertiary">Configure your LLM provider settings. Supports OpenAI-compatible APIs.</div>
</div>
<div className="grid-col grid w-full grid-cols-1 items-center justify-between gap-x-12 gap-y-8 lg:grid-cols-3">
{aiFormFields.map((field) => (
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
10 changes: 10 additions & 0 deletions apps/admin/app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import "@fontsource-variable/inter";
import interVariableWoff2 from "@fontsource-variable/inter/files/inter-latin-wght-normal.woff2?url";
import "@fontsource/material-symbols-rounded";
import "@fontsource/ibm-plex-mono";
// [FA-CUSTOM] Yekan Bakh font preload
import yekanBakhWoff2 from "@/app/assets/fonts/yekan-bakh/YekanBakh-VF.woff2?url";

const APP_TITLE = "Plane | Simple, extensible, open-source project management tool.";
const APP_DESCRIPTION =
Expand All @@ -40,6 +42,14 @@ export const links: LinksFunction = () => [
type: "font/woff2",
crossOrigin: "anonymous",
},
// [FA-CUSTOM] Preload Yekan Bakh font
{
rel: "preload",
href: yekanBakhWoff2,
as: "font",
type: "font/woff2",
crossOrigin: "anonymous",
},
];

export function Layout({ children }: { children: ReactNode }) {
Expand Down
11 changes: 11 additions & 0 deletions apps/admin/styles/fonts.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* [FA-CUSTOM] Yekan Bakh variable font for Farsi/Persian support */
@font-face {
font-family: "Yekan Bakh";
font-style: normal;
font-weight: 100 900;
font-display: swap;
src:
url("../app/assets/fonts/yekan-bakh/YekanBakh-VF.woff2") format("woff2"),
url("../app/assets/fonts/yekan-bakh/YekanBakh-VF.woff") format("woff"),
url("../app/assets/fonts/yekan-bakh/YekanBakh-VF.ttf") format("truetype");
}
1 change: 1 addition & 0 deletions apps/admin/styles/globals.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@import "@plane/tailwind-config/index.css";
@import "./fonts.css"; /* [FA-CUSTOM] Yekan Bakh font */

.shadow-custom {
box-shadow: 2px 2px 8px 2px rgba(234, 231, 250, 0.3); /* Convert #EAE7FA4D to rgba */
Expand Down
2 changes: 2 additions & 0 deletions apps/api/plane/app/serializers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@

from .exporter import ExporterHistorySerializer

from .importer_job import ImportJobSerializer # [FA-CUSTOM] file-based CSV/XLSX import

from .webhook import WebhookSerializer, WebhookLogSerializer

from .favorite import UserFavoriteSerializer
Expand Down
Loading