diff --git a/.forgejo/workflows/deploy-dev.yml b/.forgejo/workflows/deploy-dev.yml
index 26053ff..19d5a78 100644
--- a/.forgejo/workflows/deploy-dev.yml
+++ b/.forgejo/workflows/deploy-dev.yml
@@ -10,18 +10,98 @@ jobs:
steps:
- name: Check out repo
uses: https://data.forgejo.org/actions/checkout@v4
+ with:
+ fetch-depth: 0
- - name: Setup Bun
+ - name: Install Packages
+ run: apt-get update && apt-get install -y sudo curl jq
+
+ - name: Install Nix
+ uses: https://github.com/cachix/install-nix-action@v30
+
+ - name: Install Bun
uses: oven-sh/setup-bun@v2
- name: Install dependencies
run: bun install --frozen-lockfile
+ - name: Setup Android Keystore
+ env:
+ KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
+ KEYSTORE_PROPERTIES: ${{ secrets.ANDROID_KEYSTORE_PROPERTIES }}
+ run: |
+ bun -e "require('fs').writeFileSync('keystore.jks', Buffer.from(process.env.KEYSTORE_BASE64.replace(/\s+/g, ''), 'base64'))"
+ bun -e "const content = process.env.KEYSTORE_PROPERTIES.replace(/\\\\n/g, '\n').replace(/\\r/g, '').split('\n').map(l => l.trim()).filter(l => l).join('\n'); require('fs').writeFileSync('keystore.properties', content)"
+
- name: Build
- run: bun run build:web
+ run: bun run build:apps
- name: Install rsync
run: apt-get update && apt-get install -y rsync
- name: Deploy
run: rsync -a --delete apps/web/dist/ /var/lib/www/tensamin-web-dev/
+
+ - name: Read version and hash
+ id: version
+ run: |
+ VERSION="$(node -p "require('./package.json').version")"
+ SHORT_SHA="$(git rev-parse --short HEAD)"
+ echo "version=$VERSION" >> "$FORGEJO_OUTPUT"
+ echo "short_sha=$SHORT_SHA" >> "$FORGEJO_OUTPUT"
+ echo "tag=dev-${VERSION}-${SHORT_SHA}" >> "$FORGEJO_OUTPUT"
+ echo "title=${VERSION}-dev-${SHORT_SHA}" >> "$FORGEJO_OUTPUT"
+
+ - name: Create pre-release and upload files
+ env:
+ TOKEN: ${{ forgejo.token }}
+ API: ${{ forgejo.api_url }}
+ REPO: ${{ forgejo.repository }}
+ SHA: ${{ forgejo.sha }}
+ TAG: ${{ steps.version.outputs.tag }}
+ TITLE: ${{ steps.version.outputs.title }}
+ run: |
+ set -eu
+
+ test -d releases
+ find releases -type f | grep -q .
+
+ LATEST_PROD_TAG="$(git tag --list 'v*' --sort=-v:refname | head -n 1 || true)"
+ if [ -n "$LATEST_PROD_TAG" ]; then
+ BODY="$(git log "$LATEST_PROD_TAG"..HEAD --pretty=format:'- %s')"
+ else
+ BODY="$(git log --pretty=format:'- %s')"
+ fi
+
+ HTTP_STATUS=$(curl -s -w "%{http_code}" -o release_out.json -H "Authorization: token $TOKEN" "$API/repos/$REPO/releases/tags/$TAG")
+
+ if [ "$HTTP_STATUS" = "200" ]; then
+ echo "Release $TAG already exists."
+ RELEASE_ID="$(jq -r .id release_out.json)"
+ else
+ echo "Creating new pre-release for $TAG"
+ RELEASE_JSON="$(curl -f -sS -X POST "$API/repos/$REPO/releases" \
+ -H "Authorization: token $TOKEN" \
+ -H "Content-Type: application/json" \
+ -d "$(jq -n \
+ --arg tag "$TAG" \
+ --arg name "$TITLE" \
+ --arg body "$BODY" \
+ --arg target "$SHA" \
+ '{
+ tag_name: $tag,
+ name: $name,
+ body: $body,
+ target_commitish: $target,
+ draft: false,
+ prerelease: true
+ }')")"
+ RELEASE_ID="$(echo "$RELEASE_JSON" | jq -r .id)"
+ fi
+
+ find releases -type f -print0 | while IFS= read -r -d '' file; do
+ name="$(basename "$file")"
+ curl -fsS -X POST "$API/repos/$REPO/releases/$RELEASE_ID/assets?name=$name" \
+ -H "Authorization: token $TOKEN" \
+ -F "attachment=@$file"
+ done
diff --git a/.forgejo/workflows/deploy-prod.yml b/.forgejo/workflows/deploy-prod.yml
index 1df5b60..c2f5c62 100644
--- a/.forgejo/workflows/deploy-prod.yml
+++ b/.forgejo/workflows/deploy-prod.yml
@@ -54,13 +54,14 @@ jobs:
REPO: ${{ forgejo.repository }}
SHA: ${{ forgejo.sha }}
TAG: ${{ steps.version.outputs.tag }}
- VERSION: ${{ steps.version.outputs.version }}
run: |
set -eu
test -d releases
find releases -type f | grep -q .
+ COMMIT_MSG="$(git log -1 --pretty=%B)"
+
HTTP_STATUS=$(curl -s -w "%{http_code}" -o release_out.json -H "Authorization: token $TOKEN" "$API/repos/$REPO/releases/tags/$TAG")
if [ "$HTTP_STATUS" = "200" ]; then
@@ -74,7 +75,7 @@ jobs:
-d "$(jq -n \
--arg tag "$TAG" \
--arg name "$TAG" \
- --arg body "Release $VERSION" \
+ --arg body "$COMMIT_MSG" \
--arg target "$SHA" \
'{
tag_name: $tag,
diff --git a/apps/web/src/components/sidebar.tsx b/apps/web/src/components/sidebar.tsx
index ce9bb6b..6b4b6cd 100644
--- a/apps/web/src/components/sidebar.tsx
+++ b/apps/web/src/components/sidebar.tsx
@@ -6,6 +6,7 @@ import {
Sidebar as SidebarRoot,
SidebarContent,
SidebarFooter,
+ useSidebar,
} from "@tensamin/ui";
import { isTauri } from "@tauri-apps/api/core";
import { useIsMobile } from "@tensamin/ui";
@@ -13,17 +14,21 @@ import { MobileNavbar } from "./navbar";
import SidebarBox from "@tensamin/call/sidebarBox";
import { useShowMobileNavbar } from "@/routes/app/layout";
-
/**
* Renders the conversation sidebar with account summary and conversation list.
+ * On mobile the sidebar is always kept in the DOM and hidden via CSS
+ * (opacity + translateX) instead of being unmounted. This keeps the DOM and
+ * React state alive while the drawer is closed, allowing the sidebar to open
+ * instantly on subsequent toggles.
* @returns Sidebar JSX.
*/
export default function Sidebar() {
const isMobile = useIsMobile();
const showMobileNavbar = useShowMobileNavbar();
+ const { openMobile, setOpenMobile } = useSidebar();
- return (
-