Skip to content
This repository was archived by the owner on Sep 24, 2026. It is now read-only.

Commit ceb1032

Browse files
ComputelessComputerwarp-agent
andcommitted
Add dprint formatting, typecheck script, and CI check step
Co-Authored-By: Warp <agent@warp.dev>
1 parent 7469488 commit ceb1032

30 files changed

Lines changed: 1098 additions & 954 deletions

‎.github/workflows/frontend_ci.yml‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ on:
99
- "bun.lock"
1010
- "tsconfig*.json"
1111
- "vite.config.*"
12+
- "dprint.json"
1213
pull_request:
1314
branches: [main]
1415
paths:
@@ -17,6 +18,7 @@ on:
1718
- "bun.lock"
1819
- "tsconfig*.json"
1920
- "vite.config.*"
21+
- "dprint.json"
2022

2123
concurrency:
2224
group: ${{ github.workflow }}-${{ github.ref }}
@@ -36,3 +38,9 @@ jobs:
3638

3739
- name: Typecheck and build frontend
3840
run: bun run build
41+
42+
- name: Install dprint
43+
run: curl -fsSL https://dprint.dev/install.sh | sh && echo "$HOME/.dprint/bin" >> $GITHUB_PATH
44+
45+
- name: Check formatting
46+
run: dprint check

‎agents.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
# Agents
22

33
## Project Overview
4+
45
Philo is a daily journaling desktop app that lets users generate custom React mini-apps (widgets) inline via AI. Built with Tauri v2, React 19, TypeScript, TipTap, and Tailwind CSS v4.
56

67
## Stack
8+
79
- **Desktop shell**: Tauri v2 (Rust backend)
810
- **Frontend**: Vite 7 + React 19 + TypeScript 5.8
911
- **Editor**: TipTap (ProseMirror-based) with custom extensions
@@ -12,12 +14,19 @@ Philo is a daily journaling desktop app that lets users generate custom React mi
1214
- **Storage**: Tauri `fs` plugin → local filesystem (`$APPDATA/philo/`)
1315

1416
## Commands
17+
1518
- `bun run dev` — Start Vite dev server
1619
- `bun run build` — TypeScript check + Vite build
1720
- `bun run tauri dev` — Run full Tauri app in dev mode
1821
- `bun run tauri build` — Production build
22+
- `bun run typecheck` — Run TypeScript type checking (`tsc --noEmit`)
23+
- `bun run fmt` — Format code with dprint
24+
- `bun run fmt:check` — Check formatting without writing (CI)
25+
- `bun run check` — Run typecheck + format (`bun run typecheck && dprint fmt`)
26+
- `cargo fmt --manifest-path src-tauri/Cargo.toml` — Format Rust code
1927

2028
## Project Structure
29+
2130
```
2231
src/
2332
components/
@@ -35,13 +44,15 @@ src-tauri/ # Rust backend (Tauri v2)
3544
```
3645

3746
## Architecture Notes
47+
3848
- One JSON file per day stored in `$APPDATA/philo/journal/` (e.g. `2026-02-14.json`)
3949
- Daily notes contain TipTap JSON content + task metadata
4050
- Tasks have `originDate` tracking for rollover — unchecked tasks carry forward to the next day
4151
- Widget rendering uses sandboxed iframes with Sucrase for client-side TSX transpilation
4252
- Widgets communicate with host via `postMessage` (state, theme, resize)
4353

4454
## Conventions
55+
4556
- Use TypeScript strict mode
4657
- Use Tailwind CSS utility classes for styling
4758
- Use `nanoid` for generating unique IDs
@@ -50,4 +61,5 @@ src-tauri/ # Rust backend (Tauri v2)
5061
- Storage operations go through `services/storage.ts`
5162

5263
## Current Phase
64+
5365
Phase 2: Daily Journal Timeline — implementing timeline view, daily note storage, and navigation.

‎dprint.json‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"lineWidth": 120,
3+
"indentWidth": 2,
4+
"useTabs": false,
5+
"typescript": {
6+
"semiColons": "always",
7+
"quoteStyle": "preferDouble",
8+
"trailingCommas": "always"
9+
},
10+
"json": {},
11+
"markdown": {},
12+
"excludes": [
13+
"node_modules",
14+
"dist",
15+
"src-tauri/target",
16+
"src-tauri/Cargo.lock"
17+
],
18+
"plugins": [
19+
"https://plugins.dprint.dev/typescript-0.93.3.wasm",
20+
"https://plugins.dprint.dev/json-0.19.4.wasm",
21+
"https://plugins.dprint.dev/markdown-0.17.8.wasm"
22+
]
23+
}

‎package.json‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
{
22
"name": "philo",
33
"private": true,
4-
"version": "0.0.5",
4+
"version": "0.0.5",
55
"type": "module",
66
"scripts": {
77
"dev": "vite",
88
"build": "tsc && vite build",
99
"preview": "vite preview",
10+
"typecheck": "tsc --noEmit",
11+
"fmt": "dprint fmt",
12+
"fmt:check": "dprint check",
13+
"check": "bun run typecheck && dprint fmt",
1014
"tauri": "tauri"
1115
},
1216
"dependencies": {

‎postcss.config.js‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export default {
22
plugins: {
3-
'@tailwindcss/postcss': {},
3+
"@tailwindcss/postcss": {},
44
},
5-
}
5+
};

‎src-tauri/capabilities/default.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"identifier": "default",
44
"description": "Capability for the main window",
55
"windows": ["main"],
6-
"permissions": [
6+
"permissions": [
77
"core:default",
88
"opener:default",
99
"fs:default",

‎src-tauri/tauri.conf.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://schema.tauri.app/config/2",
33
"productName": "philo",
4-
"version": "0.0.5",
4+
"version": "0.0.5",
55
"identifier": "com.johnjeong.philo",
66
"build": {
77
"beforeDevCommand": "bun run dev",

‎src/components/UpdateBanner.tsx‎

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,28 @@
1-
import { useState } from 'react';
2-
import type { UpdateInfo } from '../services/updater';
1+
import { useState, } from "react";
2+
import type { UpdateInfo, } from "../services/updater";
33

44
interface UpdateBannerProps {
55
update: UpdateInfo;
66
onDismiss: () => void;
77
}
88

9-
export function UpdateBanner({ update, onDismiss }: UpdateBannerProps) {
10-
const [updating, setUpdating] = useState(false);
9+
export function UpdateBanner({ update, onDismiss, }: UpdateBannerProps,) {
10+
const [updating, setUpdating,] = useState(false,);
1111

1212
const handleUpdate = async () => {
13-
setUpdating(true);
13+
setUpdating(true,);
1414
try {
1515
await update.downloadAndInstall();
1616
} catch (err) {
17-
console.error('Update failed:', err);
18-
setUpdating(false);
17+
console.error("Update failed:", err,);
18+
setUpdating(false,);
1919
}
2020
};
2121

2222
return (
2323
<div
2424
className="sticky top-0 z-50 flex items-center justify-center gap-3 px-4 py-2 border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800/80"
25-
style={{ fontFamily: "'IBM Plex Mono', monospace" }}
25+
style={{ fontFamily: "'IBM Plex Mono', monospace", }}
2626
>
2727
<span className="text-xs text-gray-600 dark:text-gray-300">
2828
v{update.version} available
@@ -34,11 +34,11 @@ export function UpdateBanner({ update, onDismiss }: UpdateBannerProps) {
3434
className="text-xs px-2.5 py-0.5 rounded-md text-white transition-all cursor-pointer disabled:opacity-60 disabled:cursor-default"
3535
style={{
3636
background: updating
37-
? 'linear-gradient(to bottom, #9ca3af, #6b7280)'
38-
: 'linear-gradient(to bottom, #4b5563, #1f2937)',
37+
? "linear-gradient(to bottom, #9ca3af, #6b7280)"
38+
: "linear-gradient(to bottom, #4b5563, #1f2937)",
3939
}}
4040
>
41-
{updating ? 'Updating\u2026' : 'Update & Restart'}
41+
{updating ? "Updating\u2026" : "Update & Restart"}
4242
</button>
4343

4444
{!updating && (

‎src/components/editor/EditorBubbleMenu.tsx‎

Lines changed: 38 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { useState } from 'react';
2-
import { BubbleMenu } from '@tiptap/react/menus';
3-
import type { Editor } from '@tiptap/core';
4-
import { generateWidget } from '../../services/generate';
1+
import type { Editor, } from "@tiptap/core";
2+
import { BubbleMenu, } from "@tiptap/react/menus";
3+
import { useState, } from "react";
4+
import { generateWidget, } from "../../services/generate";
55

66
interface EditorBubbleMenuProps {
77
editor: Editor;
@@ -10,34 +10,34 @@ interface EditorBubbleMenuProps {
1010
/**
1111
* Find a widget node by ID and update its attributes.
1212
*/
13-
function updateWidgetById(editor: Editor, id: string, attrs: Record<string, unknown>) {
14-
const { doc } = editor.state;
13+
function updateWidgetById(editor: Editor, id: string, attrs: Record<string, unknown>,) {
14+
const { doc, } = editor.state;
1515
const tr = editor.state.tr;
1616
let found = false;
1717

18-
doc.descendants((node, pos) => {
18+
doc.descendants((node, pos,) => {
1919
if (found) return false;
20-
if (node.type.name === 'widget' && node.attrs.id === id) {
21-
tr.setNodeMarkup(pos, undefined, { ...node.attrs, ...attrs });
20+
if (node.type.name === "widget" && node.attrs.id === id) {
21+
tr.setNodeMarkup(pos, undefined, { ...node.attrs, ...attrs, },);
2222
found = true;
2323
return false;
2424
}
25-
});
25+
},);
2626

2727
if (found) {
28-
editor.view.dispatch(tr);
28+
editor.view.dispatch(tr,);
2929
}
3030
}
3131

32-
export function EditorBubbleMenu({ editor }: EditorBubbleMenuProps) {
33-
const [building, setBuilding] = useState(false);
32+
export function EditorBubbleMenu({ editor, }: EditorBubbleMenuProps,) {
33+
const [building, setBuilding,] = useState(false,);
3434

3535
const handleBuild = async () => {
36-
const { from, to } = editor.state.selection;
37-
const selectedText = editor.state.doc.textBetween(from, to);
36+
const { from, to, } = editor.state.selection;
37+
const selectedText = editor.state.doc.textBetween(from, to,);
3838
if (!selectedText.trim() || building) return;
3939

40-
setBuilding(true);
40+
setBuilding(true,);
4141

4242
// Insert loading widget, replacing selection
4343
const widgetId = crypto.randomUUID();
@@ -46,63 +46,65 @@ export function EditorBubbleMenu({ editor }: EditorBubbleMenuProps) {
4646
.focus()
4747
.deleteSelection()
4848
.insertContent({
49-
type: 'widget',
50-
attrs: { id: widgetId, prompt: selectedText, spec: '', loading: true, saved: false, error: '' },
51-
})
49+
type: "widget",
50+
attrs: { id: widgetId, prompt: selectedText, spec: "", loading: true, saved: false, error: "", },
51+
},)
5252
.run();
5353

5454
try {
55-
const spec = await generateWidget(selectedText);
56-
updateWidgetById(editor, widgetId, { spec: JSON.stringify(spec), loading: false });
55+
const spec = await generateWidget(selectedText,);
56+
updateWidgetById(editor, widgetId, { spec: JSON.stringify(spec,), loading: false, },);
5757
} catch (err) {
58-
console.error('Build failed:', err);
59-
const msg = err instanceof Error && err.message === 'API_KEY_MISSING'
60-
? 'No API key. Add your Anthropic key in Settings (⌘,).'
61-
: err instanceof Error ? err.message : 'Something went wrong.';
62-
updateWidgetById(editor, widgetId, { loading: false, error: msg });
58+
console.error("Build failed:", err,);
59+
const msg = err instanceof Error && err.message === "API_KEY_MISSING"
60+
? "No API key. Add your Anthropic key in Settings (⌘,)."
61+
: err instanceof Error
62+
? err.message
63+
: "Something went wrong.";
64+
updateWidgetById(editor, widgetId, { loading: false, error: msg, },);
6365
} finally {
64-
setBuilding(false);
66+
setBuilding(false,);
6567
}
6668
};
6769

6870
return (
6971
<BubbleMenu
7072
editor={editor}
71-
options={{ placement: 'top', offset: 8 }}
72-
shouldShow={({ from, to }: { from: number; to: number }) => from !== to}
73+
options={{ placement: "top", offset: 8, }}
74+
shouldShow={({ from, to, }: { from: number; to: number; },) => from !== to}
7375
>
7476
<div className="bubble-menu">
7577
<button
7678
onClick={() => editor.chain().focus().toggleBold().run()}
77-
className={`bubble-btn ${editor.isActive('bold') ? 'bubble-btn-active' : ''}`}
79+
className={`bubble-btn ${editor.isActive("bold",) ? "bubble-btn-active" : ""}`}
7880
>
7981
B
8082
</button>
8183
<button
8284
onClick={() => editor.chain().focus().toggleItalic().run()}
83-
className={`bubble-btn bubble-btn-italic ${editor.isActive('italic') ? 'bubble-btn-active' : ''}`}
85+
className={`bubble-btn bubble-btn-italic ${editor.isActive("italic",) ? "bubble-btn-active" : ""}`}
8486
>
8587
I
8688
</button>
8789
<button
8890
onClick={() => editor.chain().focus().toggleStrike().run()}
89-
className={`bubble-btn bubble-btn-strike ${editor.isActive('strike') ? 'bubble-btn-active' : ''}`}
91+
className={`bubble-btn bubble-btn-strike ${editor.isActive("strike",) ? "bubble-btn-active" : ""}`}
9092
>
9193
S
9294
</button>
9395
<button
9496
onClick={() => editor.chain().focus().toggleCode().run()}
95-
className={`bubble-btn ${editor.isActive('code') ? 'bubble-btn-active' : ''}`}
97+
className={`bubble-btn ${editor.isActive("code",) ? "bubble-btn-active" : ""}`}
9698
>
97-
{'</>'}
99+
{"</>"}
98100
</button>
99101
<div className="bubble-divider" />
100102
<button
101103
onClick={handleBuild}
102-
className={`bubble-btn bubble-btn-build ${building ? 'bubble-btn-building' : ''}`}
104+
className={`bubble-btn bubble-btn-build ${building ? "bubble-btn-building" : ""}`}
103105
disabled={building}
104106
>
105-
{building ? 'Building...' : 'Build ⌘↵'}
107+
{building ? "Building..." : "Build ⌘↵"}
106108
</button>
107109
</div>
108110
</BubbleMenu>

0 commit comments

Comments
 (0)