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
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
/packages/tasks/ @tomymaritano
/packages/commands/ @tomymaritano
/packages/embeds/ @tomymaritano
/packages/design-system/ @tomymaritano
/packages/plugin-api/ @tomymaritano

# Desktop app (proprietary) - stricter review
/apps/desktop/ @tomymaritano
Expand Down
73 changes: 73 additions & 0 deletions .github/workflows/deploy-api.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: Deploy API

on:
push:
branches: [main]
paths:
- 'packages/api/**'
- '.github/workflows/deploy-api.yml'
workflow_dispatch:
inputs:
environment:
description: 'Deploy environment'
required: true
default: 'production'
type: choice
options:
- staging
- production

jobs:
test:
name: Test API
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4

- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'pnpm'

- run: pnpm install

- name: Typecheck
run: pnpm --filter @readied/api typecheck

- name: Test
run: pnpm --filter @readied/api test

deploy:
name: Deploy API
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4

- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'pnpm'

- run: pnpm install

- name: Determine environment
id: env
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "target=${{ inputs.environment }}" >> "$GITHUB_OUTPUT"
else
echo "target=production" >> "$GITHUB_OUTPUT"
fi

- name: Deploy to Cloudflare Workers
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
DEPLOY_ENV: ${{ steps.env.outputs.target }}
run: npx wrangler deploy --env "$DEPLOY_ENV"
working-directory: packages/api
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Readied uses an **Open Core** model:
| `packages/tasks` | Task/checkbox parsing |
| `packages/commands` | Command palette |
| `packages/embeds` | Image/embed handling |
| `packages/design-system` | Design tokens, components |
| `packages/plugin-api` | Plugin API + theme system |

### Proprietary - Not Open for Contributions

Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ The following packages are licensed under the MIT License:
- `packages/tasks/` - Task parsing
- `packages/commands/` - Command palette logic
- `packages/embeds/` - Embed handling
- `packages/design-system/` - Design tokens and components
- `packages/plugin-api/` - Plugin API and theme system
- `packages/product-config/` - Product configuration

See the LICENSE file in each package directory for the full MIT License text.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pnpm dev
| `@readied/tasks` | Task/checkbox parsing |
| `@readied/commands` | Command palette |
| `@readied/embeds` | Image/embed handling |
| `@readied/design-system` | Design tokens, components |
| `@readied/plugin-api` | Plugin API + theme system |

## Contributing

Expand Down
26 changes: 25 additions & 1 deletion apps/desktop/src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -979,13 +979,24 @@ function registerNotebookHandlers(): void {
};
});

// Move notebook
// Move notebook (recursively updates children's depth)
ipcMain.handle('notebooks:move', async (_event, id: string, newParentId: string | null) => {
const notebook = await repo.get(createNotebookId(id));
if (!notebook) {
throw new Error('Notebook not found');
}

// Prevent circular reference: can't move a notebook into its own descendant
if (newParentId) {
let current = await repo.get(createNotebookId(newParentId));
while (current && current.parentId) {
if (current.parentId === notebook.id) {
throw new Error('CIRCULAR_REFERENCE');
}
current = await repo.get(current.parentId);
}
}

let newParentDepth = 0;
if (newParentId) {
const parent = await repo.get(createNotebookId(newParentId));
Expand All @@ -1006,6 +1017,19 @@ function registerNotebookHandlers(): void {

await repo.save(result.notebook);

// Recursively update children's depth to match the new hierarchy
const updateChildrenDepth = async (parentId: string, parentDepth: number) => {
const children = await repo.getChildren(parentId as ReturnType<typeof createNotebookId>);
for (const child of children) {
const newChildDepth = parentDepth + 1;
if (child.depth !== newChildDepth) {
await repo.save({ ...child, depth: newChildDepth });
await updateChildrenDepth(child.id, newChildDepth);
}
}
};
await updateChildrenDepth(result.notebook.id, result.notebook.depth);

return {
id: result.notebook.id,
name: result.notebook.name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,11 @@
}

.successIcon {
color: #10b981;
color: var(--success, #10b981);
}

.errorIcon {
color: #ef4444;
color: var(--danger, #ef4444);
}

.spinner {
Expand Down Expand Up @@ -181,6 +181,13 @@
background: var(--bg-hover);
}

.errorText {
color: var(--danger, #ef4444);
font-size: 0.875rem;
text-align: center;
margin: 0 0 0.75rem;
}

.actions {
display: flex;
flex-direction: column;
Expand Down
25 changes: 21 additions & 4 deletions apps/desktop/src/renderer/components/auth/MagicLinkFlow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* Multi-step dialog for passwordless authentication via email magic link.
*/

import { useState, useCallback, FormEvent } from 'react';
import { useState, useCallback, useEffect, FormEvent } from 'react';
import { Mail, CheckCircle, AlertCircle, X } from 'lucide-react';
import { useAuthStore } from '../../stores/authStore';
import styles from './MagicLinkFlow.module.css';
Expand All @@ -16,13 +16,28 @@ export interface MagicLinkFlowProps {

type Step = 'email' | 'sent' | 'verifying' | 'success' | 'error';

export function MagicLinkFlow({ onSuccess: _onSuccess, onCancel }: MagicLinkFlowProps) {
const { requestMagicLink, error: authError } = useAuthStore();
export function MagicLinkFlow({ onSuccess, onCancel }: MagicLinkFlowProps) {
const { requestMagicLink, isAuthenticated, error: authError } = useAuthStore();
const [step, setStep] = useState<Step>('email');
const [email, setEmail] = useState('');
const [error, setError] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);

// Watch for auth success (deep link verified in background)
useEffect(() => {
if (isAuthenticated && step === 'sent') {
setStep('success');
onSuccess();
}
}, [isAuthenticated, step, onSuccess]);

// Watch for verification errors from the deep link path
useEffect(() => {
if (authError && step === 'sent') {
setError(authError);
}
}, [authError, step]);

const handleSubmitEmail = useCallback(
async (e: FormEvent) => {
e.preventDefault();
Expand Down Expand Up @@ -100,10 +115,12 @@ export function MagicLinkFlow({ onSuccess: _onSuccess, onCancel }: MagicLinkFlow
<h2 className={styles.title}>Check your email</h2>
<p className={styles.description}>
We sent a magic link to <strong>{email}</strong>. Click the link in the email to
sign in.
sign in. This window will update automatically.
</p>
</div>

{error && <p className={styles.errorText}>{error}</p>}

<div className={styles.actions}>
<button type="button" className={styles.secondaryButton} onClick={handleRetry}>
Use different email
Expand Down
Loading
Loading