ijihun-planner-studio 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.env.example ADDED
@@ -0,0 +1,5 @@
1
+ PLANNER_OWNER_EMAIL=leejihun04@gmail.com
2
+ PLANNER_PASSWORD_HASH=scrypt$...
3
+ PLANNER_SESSION_SECRET=replace-with-random-secret
4
+ PLANNER_COOKIE_SECURE=0
5
+ PLANNER_REMINDERS_BRIDGE_ROOT=/Users/ijihun/apps/icloud-reminders-google-sync
package/README.md ADDED
@@ -0,0 +1,40 @@
1
+ # Planner Studio
2
+
3
+ Owner-only local planner for timeboxing, Mandarart planning, Apple Reminders, and the existing Google Tasks bridge.
4
+
5
+ ## Run
6
+
7
+ ```bash
8
+ npm install -g ijihun-planner-studio
9
+ planner-studio serve --port 4179
10
+ ```
11
+
12
+ Open `http://127.0.0.1:4179/`.
13
+
14
+ ## Owner Login Setup
15
+
16
+ Create `.env.local` in the working directory. The setup command reads the password from stdin and stores only a `scrypt` hash plus a session signing secret.
17
+
18
+ ```bash
19
+ planner-studio setup --email leejihun04@gmail.com --env .env.local < password.txt
20
+ chmod 600 .env.local
21
+ ```
22
+
23
+ Required environment values:
24
+
25
+ ```bash
26
+ PLANNER_OWNER_EMAIL=leejihun04@gmail.com
27
+ PLANNER_PASSWORD_HASH=scrypt$...
28
+ PLANNER_SESSION_SECRET=...
29
+ PLANNER_COOKIE_SECURE=0
30
+ PLANNER_REMINDERS_BRIDGE_ROOT=/Users/ijihun/apps/icloud-reminders-google-sync
31
+ ```
32
+
33
+ Use `PLANNER_COOKIE_SECURE=1` only when the app is served over HTTPS. The default server binds to `127.0.0.1`.
34
+
35
+ ## Security Notes
36
+
37
+ - The owner password is never shipped in the browser bundle.
38
+ - The app uses an HttpOnly `SameSite=Lax` session cookie and requires a CSRF header for state-changing API calls.
39
+ - Security headers include CSP, `X-Frame-Options: DENY`, `X-Content-Type-Options: nosniff`, `Referrer-Policy: no-referrer`, and a restrictive `Permissions-Policy`.
40
+ - Planner content is local browser data. Treat the macOS user account and browser profile as part of the trust boundary.
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from "node:child_process";
3
+ import { dirname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
7
+ const args = process.argv.slice(2);
8
+ const command = args[0] || "serve";
9
+
10
+ if (command === "help" || command === "--help" || command === "-h") {
11
+ printHelp();
12
+ process.exit(0);
13
+ }
14
+
15
+ if (command === "setup") {
16
+ runNode(join(packageRoot, "scripts/create-owner-secret.mjs"), args.slice(1));
17
+ } else if (command === "serve") {
18
+ runNode(join(packageRoot, "server.mjs"), args.slice(1));
19
+ } else {
20
+ runNode(join(packageRoot, "server.mjs"), args);
21
+ }
22
+
23
+ function runNode(entry, entryArgs) {
24
+ const child = spawn(process.execPath, [entry, ...entryArgs], {
25
+ cwd: process.cwd(),
26
+ env: process.env,
27
+ stdio: "inherit"
28
+ });
29
+ child.on("exit", (code, signal) => {
30
+ if (signal) process.kill(process.pid, signal);
31
+ process.exit(code ?? 0);
32
+ });
33
+ }
34
+
35
+ function printHelp() {
36
+ console.log(`Planner Studio
37
+
38
+ Usage:
39
+ planner-studio serve [--host 127.0.0.1] [--port 4179]
40
+ planner-studio setup --email leejihun04@gmail.com [--env .env.local] < password
41
+
42
+ Environment:
43
+ PLANNER_OWNER_EMAIL Owner login email
44
+ PLANNER_PASSWORD_HASH scrypt password hash generated by setup
45
+ PLANNER_SESSION_SECRET Session cookie signing secret generated by setup
46
+ PLANNER_COOKIE_SECURE=1 Use only when serving over HTTPS
47
+ PLANNER_REMINDERS_BRIDGE_ROOT Apple Reminders bridge folder
48
+ `);
49
+ }
@@ -0,0 +1 @@
1
+ :root{color:#1f1f23;background:#eef2f7;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Apple SD Gothic Neo,Noto Sans KR,Segoe UI,sans-serif;font-synthesis:none;text-rendering:optimizeLegibility;--ink: #1f1f23;--muted: #72737a;--line: #d7dbe3;--heavy-line: #25252a;--paper: #ffffff;--panel: #f8fafc;--cobalt: #315ddc;--green: #26886e;--yellow: #f4c84f;--danger: #be3a3a}*{box-sizing:border-box}body{margin:0;min-width:320px;min-height:100vh}button,input,select,textarea{font:inherit}button{border:1px solid var(--line);border-radius:8px;background:#fff;color:var(--ink);cursor:pointer;min-height:34px;display:inline-flex;align-items:center;justify-content:center;gap:7px;padding:0 12px;font-size:13px;font-weight:750}button:disabled{cursor:not-allowed;opacity:.56}input,select,textarea{border:1px solid var(--line);border-radius:8px;color:var(--ink);background:#fff;min-height:34px}textarea{resize:vertical}.login-shell{min-height:100vh;display:grid;place-items:center;padding:24px}.login-card{width:min(430px,100%);background:#fff;border:1px solid var(--line);border-radius:8px;box-shadow:0 20px 50px #181c2624;padding:28px}.login-mark{width:46px;height:46px;border:2px solid var(--ink);border-radius:8px;display:grid;place-items:center;margin-bottom:18px}.login-card h1{margin:4px 0 0;font-size:30px;line-height:1.08}.login-card p{margin:12px 0 20px;color:var(--muted);line-height:1.5}.login-error{border:1px solid #f0b8b8;background:#fff6f6;color:#9c2f2f!important;border-radius:8px;padding:10px 12px;font-size:13px}.login-form{display:grid;gap:12px}.login-form label{display:grid;gap:6px;font-size:13px;font-weight:800;color:#4f5159}.login-form input{min-height:42px;padding:0 12px}.login-form button{min-height:42px;background:var(--ink);color:#fff;border-color:var(--ink)}.app-shell{display:grid;grid-template-columns:248px minmax(700px,1fr) 348px;gap:14px;min-height:100vh;padding:14px}.status-rail,.reminder-panel,.block-editor,.mandarart-actions-panel{background:var(--panel);border:1px solid var(--line);border-radius:8px}.status-rail{display:flex;flex-direction:column;gap:14px;padding:14px;position:sticky;top:14px;height:calc(100vh - 28px);overflow:auto}.brand-block{display:grid;grid-template-columns:42px 1fr;gap:10px;align-items:center}.brand-mark{width:42px;height:42px;border:2px solid var(--ink);border-radius:8px;display:grid;place-items:center;font-weight:900;background:#fff}.brand-block strong,.brand-block span{display:block}.brand-block strong{font-size:15px}.brand-block span,.rail-note,.sync-panel dd,.sync-panel dt,.section-kicker{color:var(--muted);font-size:12px}.date-card{display:grid;gap:8px;background:#fff;border:1px solid var(--line);border-radius:8px;padding:12px}.date-card>span,.sync-heading,.panel-title-row{display:flex;align-items:center;gap:8px}.date-card input{width:100%;padding:0 10px}.date-card strong{font-size:22px}.mode-switch{display:grid;gap:8px}.mode-switch button{justify-content:flex-start;min-height:42px}.mode-switch button.active{border-color:var(--ink);background:var(--ink);color:#fff}.sync-panel{background:#fff;border:1px solid var(--line);border-radius:8px;padding:12px}.sync-heading{justify-content:space-between;margin-bottom:10px}.sync-heading strong{margin-right:auto}.sync-panel dl{display:grid;gap:9px;margin:0}.sync-panel dl>div{display:flex;align-items:baseline;justify-content:space-between;gap:8px}.sync-panel dd{margin:0;color:var(--ink);text-align:right}.rail-actions{display:grid;gap:8px}.rail-actions button,.file-button{justify-content:flex-start;min-height:38px}.file-button{border:1px solid var(--line);border-radius:8px;background:#fff;padding:0 12px;display:flex;align-items:center;gap:7px;cursor:pointer;font-size:13px;font-weight:750}.file-button input{display:none}.rail-note{margin-top:auto;display:flex;gap:8px;line-height:1.45}.main-column{min-width:0;display:grid;grid-template-rows:auto minmax(0,1fr);gap:12px}.topbar{min-height:68px;display:flex;align-items:center;justify-content:space-between;gap:12px;background:#fff;border:1px solid var(--line);border-radius:8px;padding:12px 14px}.topbar h2,.panel-title-row h2{margin:1px 0 0;font-size:20px;line-height:1.15}.topbar-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap;justify-content:flex-end}.status-pill{border:1px solid var(--line);border-radius:999px;background:#f9fbff;padding:8px 10px;font-size:12px;font-weight:800}.workspace{min-width:0;min-height:0}.timebox-workspace{display:grid;grid-template-columns:minmax(0,1fr);gap:12px}.planner-sheet{background:var(--paper);border:1px solid var(--line);border-radius:8px;box-shadow:0 14px 34px #23272f14}.timebox-sheet{padding:28px;min-height:calc(100vh - 108px)}.sheet-header{display:flex;justify-content:space-between;gap:18px;align-items:flex-start;margin-bottom:24px}.sheet-header h1{margin:0;font-size:clamp(34px,4vw,66px);line-height:.96;font-weight:950}.sheet-header span,.date-line span{color:var(--muted)}.date-line{display:flex;align-items:baseline;gap:12px;border-bottom:2px solid var(--ink);min-width:240px;padding-bottom:8px;font-size:18px}.date-line strong{font-size:24px;font-weight:900}.timebox-grid-layout{display:grid;grid-template-columns:minmax(260px,.93fr) minmax(360px,1fr);grid-template-rows:auto auto 1fr auto;gap:18px 24px;grid-template-areas:"top gratitude" "brain time" "brain time" "brain memo"}.paper-section{min-width:0}.paper-section h2{margin:0 0 12px;font-size:18px;font-weight:900;border-bottom:2px solid var(--ink);padding-bottom:10px;display:flex;align-items:center;gap:8px}.top-three{grid-area:top}.gratitude{grid-area:gratitude}.brain-dump{grid-area:brain}.time-plan{grid-area:time}.memo{grid-area:memo}.top-input{display:grid;grid-template-columns:24px minmax(0,1fr) 36px;gap:8px;align-items:center;border-bottom:1px solid var(--line);min-height:46px}.top-input span{font-weight:900;color:var(--cobalt)}.top-input input,.gratitude input{border:0;border-radius:0;min-width:0}.top-input input:focus,.gratitude input:focus,.brain-dump textarea:focus,.memo textarea:focus,.time-block input:focus,.mandarart-cell:focus{outline:2px solid rgba(49,93,220,.2);outline-offset:-2px}.top-input button{width:32px;min-height:30px;padding:0}.brain-dump textarea{width:100%;min-height:720px;border:0;border-radius:0;line-height:30px;background-image:linear-gradient(#eef1f5 1px,transparent 1px),linear-gradient(90deg,#f4f6f8 1px,transparent 1px);background-size:32px 30px;padding:10px 8px}.gratitude input{width:100%;border-bottom:1px solid var(--line)}.section-toolbar{display:flex;justify-content:space-between;gap:10px;align-items:flex-start}.section-toolbar h2{flex:1}.timeline-wrap{position:relative}.minute-labels{display:grid;grid-template-columns:44px repeat(6,1fr);color:#b5bac5;font-size:11px;border-bottom:1px solid var(--line)}.minute-labels span{text-align:right;padding:0 4px 3px 0}.timeline-grid{position:relative;border-bottom:2px solid var(--ink);background:repeating-linear-gradient(to right,transparent 0,transparent calc((100% - 44px) / 6 - 1px),#e9edf2 calc((100% - 44px) / 6)),linear-gradient(to right,transparent 0 44px,#f8fafc 44px 100%)}.hour-row{display:grid;grid-template-columns:44px 1fr;height:46px;border-bottom:1px solid #d2d6de}.hour-row span{font-weight:800;padding-top:7px;color:var(--ink)}.time-block{position:absolute;left:50px;right:8px;border:1px solid rgba(49,93,220,.35);border-left:4px solid var(--cobalt);background:#315ddc14;border-radius:8px;display:grid;grid-template-columns:minmax(0,1fr) auto 24px;align-items:center;gap:8px;padding:5px 6px;overflow:hidden}.time-block.priority{border-color:#f4c84fb8;border-left-color:var(--yellow);background:#f4c84f29}.time-block input{min-width:0;min-height:24px;border:0;background:transparent;font-weight:800}.time-block span{color:var(--muted);font-size:12px;white-space:nowrap}.time-block button{width:22px;min-height:22px;padding:0;background:transparent}.memo textarea{min-height:142px;width:100%;border:0;border-radius:0;line-height:34px;background-image:linear-gradient(#d8dce3 1px,transparent 1px);background-size:100% 34px;padding:6px 0}.block-editor{padding:14px;overflow:auto}.panel-title-row{justify-content:space-between;margin-bottom:14px}.block-list,.action-list,.reminder-list{display:grid;gap:8px}.block-row{display:grid;grid-template-columns:minmax(140px,1fr) 88px 88px 74px 34px 34px;gap:6px;align-items:center;background:#fff;border:1px solid var(--line);border-radius:8px;padding:8px}.block-row input{min-width:0;padding:0 8px}.block-row label{display:flex;align-items:center;gap:4px;font-size:12px;color:var(--muted)}.block-row button{width:32px;padding:0}.reminder-panel{position:sticky;top:14px;height:calc(100vh - 28px);padding:14px;display:flex;flex-direction:column;min-width:0}.select-row{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:8px;margin-bottom:10px}.select-row label{display:grid;gap:4px;color:var(--muted);font-size:12px}.select-row select{width:100%;padding:0 8px}.metric-strip{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:10px}.metric-strip div{background:#fff;border:1px solid var(--line);border-radius:8px;padding:10px}.metric-strip strong,.metric-strip span{display:block}.metric-strip strong{font-size:22px;line-height:1}.metric-strip span{color:var(--muted);font-size:12px;margin-top:3px}.quick-create{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:8px;margin-bottom:10px}.quick-create input{min-width:0;padding:0 10px}.error-text{margin:0 0 10px;color:var(--danger);font-size:13px}.reminder-list{overflow:auto;padding-right:2px;flex:1}.reminder-row,.action-row{background:#fff;border:1px solid var(--line);border-radius:8px;padding:10px;display:grid;gap:9px}.reminder-row strong,.reminder-row span{display:block}.reminder-row strong{line-height:1.25;overflow-wrap:anywhere}.reminder-row span{color:var(--muted);font-size:12px;margin-top:4px}.row-actions{display:grid;grid-template-columns:repeat(3,1fr);gap:6px}.row-actions button,.action-row button{min-height:30px;padding:0}.google-sync-box{margin-top:12px;border-top:1px solid var(--line);padding-top:12px;display:grid;gap:8px}.google-sync-box span{color:var(--muted);font-size:12px;line-height:1.42}.google-sync-box div{display:grid;grid-template-columns:1fr 1fr;gap:8px}.empty-state{background:#fff;border:1px dashed #c9ced7;border-radius:8px;padding:18px;display:grid;gap:6px;color:var(--muted);text-align:center}.empty-state strong{color:var(--ink)}.empty-state.compact{padding:14px;font-size:13px}.mandarart-workspace{display:grid;grid-template-columns:minmax(0,1fr);gap:12px}.mandarart-sheet{padding:28px}.mandarart-grid{width:min(100%,860px);aspect-ratio:1;margin:0 auto;display:grid;grid-template-columns:repeat(9,minmax(0,1fr));grid-template-rows:repeat(9,minmax(0,1fr));border:2px solid var(--heavy-line)}.mandarart-cell{width:100%;height:100%;min-height:0;border:0;border-right:1px solid var(--heavy-line);border-bottom:1px solid var(--heavy-line);border-radius:0;resize:none;padding:8px;text-align:center;display:block;font-size:clamp(11px,.95vw,14px);line-height:1.28;overflow:hidden}.mandarart-cell.theme-cell{background:#e8e8e8;font-weight:850}.mandarart-cell.main-goal{background:var(--ink);color:#fff;font-weight:900}.mandarart-cell.thick-left{border-left:2px solid var(--heavy-line)}.mandarart-cell.thick-top{border-top:2px solid var(--heavy-line)}.mandarart-cell.thick-right{border-right:2px solid var(--heavy-line)}.mandarart-cell.thick-bottom{border-bottom:2px solid var(--heavy-line)}.mandarart-actions-panel{padding:14px;overflow:auto}.mandarart-actions-panel p{margin:0 0 14px;color:var(--muted);font-size:13px;line-height:1.5}.action-row{grid-template-columns:minmax(0,1fr) auto;align-items:center}.action-row span{overflow-wrap:anywhere;line-height:1.35}.action-row div{display:flex;gap:6px}.wide-action{width:100%;margin-top:12px;background:var(--ink);color:#fff;border-color:var(--ink)}.icon-button{width:34px;min-height:34px;padding:0}.toast{position:fixed;left:50%;bottom:18px;transform:translate(-50%);display:flex;align-items:center;gap:8px;max-width:min(560px,calc(100vw - 28px));background:#fff;border:1px solid var(--line);border-radius:8px;box-shadow:0 16px 40px #14171e29;padding:11px 14px;z-index:20;font-size:13px;font-weight:750}.toast.ok svg{color:var(--green)}.toast.error svg{color:var(--danger)}.spin{animation:spin .9s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}@media(max-width:1240px){.app-shell{grid-template-columns:220px minmax(620px,1fr)}.reminder-panel{grid-column:1 / -1;position:static;height:auto;max-height:520px}}@media(max-width:920px){.app-shell,.timebox-workspace,.mandarart-workspace,.timebox-grid-layout{grid-template-columns:1fr}.app-shell{padding:8px}.status-rail{position:static;height:auto}.timebox-grid-layout{grid-template-areas:"top" "gratitude" "brain" "time" "memo"}.timebox-sheet,.mandarart-sheet{padding:18px}.brain-dump textarea{min-height:260px}.block-row{grid-template-columns:1fr 1fr}.mandarart-grid{width:100%}}@media print{body{background:#fff}.status-rail,.reminder-panel,.topbar,.block-editor,.mandarart-actions-panel,.toast{display:none}.app-shell,.main-column,.workspace,.timebox-workspace,.mandarart-workspace{display:block;padding:0}.planner-sheet{border:0;box-shadow:none;min-height:auto}}