@unbrained/pm-web 1.0.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.
Files changed (150) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +107 -0
  3. package/dist/auth.js +20 -0
  4. package/dist/auth.js.map +1 -0
  5. package/dist/crypto.js +42 -0
  6. package/dist/crypto.js.map +1 -0
  7. package/dist/db.js +111 -0
  8. package/dist/db.js.map +1 -0
  9. package/dist/index.js +88 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/middleware/auth.js +16 -0
  12. package/dist/middleware/auth.js.map +1 -0
  13. package/dist/routes/admin.js +207 -0
  14. package/dist/routes/admin.js.map +1 -0
  15. package/dist/routes/auth.js +163 -0
  16. package/dist/routes/auth.js.map +1 -0
  17. package/dist/routes/github.js +354 -0
  18. package/dist/routes/github.js.map +1 -0
  19. package/dist/routes/groups.js +180 -0
  20. package/dist/routes/groups.js.map +1 -0
  21. package/dist/routes/pm.js +2446 -0
  22. package/dist/routes/pm.js.map +1 -0
  23. package/dist/routes/projects.js +151 -0
  24. package/dist/routes/projects.js.map +1 -0
  25. package/dist/routes/sharing.js +155 -0
  26. package/dist/routes/sharing.js.map +1 -0
  27. package/dist/server.js +64 -0
  28. package/dist/server.js.map +1 -0
  29. package/dist/services/pm-runner.js +190 -0
  30. package/dist/services/pm-runner.js.map +1 -0
  31. package/dist/services/sse.js +111 -0
  32. package/dist/services/sse.js.map +1 -0
  33. package/manifest.json +15 -0
  34. package/package.json +111 -0
  35. package/public/icons/icon-192.png +0 -0
  36. package/public/icons/icon-512.png +0 -0
  37. package/public/index.html +265 -0
  38. package/public/manifest.json +66 -0
  39. package/public/src/api.js +28 -0
  40. package/public/src/api.js.map +1 -0
  41. package/public/src/api.ts +29 -0
  42. package/public/src/app.js +926 -0
  43. package/public/src/app.js.map +1 -0
  44. package/public/src/app.ts +929 -0
  45. package/public/src/components/modals.js +62 -0
  46. package/public/src/components/modals.js.map +1 -0
  47. package/public/src/components/modals.ts +73 -0
  48. package/public/src/components/toast.js +10 -0
  49. package/public/src/components/toast.js.map +1 -0
  50. package/public/src/components/toast.ts +13 -0
  51. package/public/src/constants.js +30 -0
  52. package/public/src/constants.js.map +1 -0
  53. package/public/src/constants.ts +41 -0
  54. package/public/src/state.js +15 -0
  55. package/public/src/state.js.map +1 -0
  56. package/public/src/state.ts +19 -0
  57. package/public/src/types.js +5 -0
  58. package/public/src/types.js.map +1 -0
  59. package/public/src/types.ts +253 -0
  60. package/public/src/utils.js +57 -0
  61. package/public/src/utils.js.map +1 -0
  62. package/public/src/utils.ts +56 -0
  63. package/public/src/views/activity.js +47 -0
  64. package/public/src/views/activity.js.map +1 -0
  65. package/public/src/views/activity.ts +41 -0
  66. package/public/src/views/admin.js +435 -0
  67. package/public/src/views/admin.js.map +1 -0
  68. package/public/src/views/admin.ts +504 -0
  69. package/public/src/views/auth.js +81 -0
  70. package/public/src/views/auth.js.map +1 -0
  71. package/public/src/views/auth.ts +74 -0
  72. package/public/src/views/calendar.js +133 -0
  73. package/public/src/views/calendar.js.map +1 -0
  74. package/public/src/views/calendar.ts +129 -0
  75. package/public/src/views/comments-audit.js +109 -0
  76. package/public/src/views/comments-audit.js.map +1 -0
  77. package/public/src/views/comments-audit.ts +108 -0
  78. package/public/src/views/config.js +322 -0
  79. package/public/src/views/config.js.map +1 -0
  80. package/public/src/views/config.ts +344 -0
  81. package/public/src/views/context.js +98 -0
  82. package/public/src/views/context.js.map +1 -0
  83. package/public/src/views/context.ts +100 -0
  84. package/public/src/views/create.js +293 -0
  85. package/public/src/views/create.js.map +1 -0
  86. package/public/src/views/create.ts +246 -0
  87. package/public/src/views/dedupe.js +51 -0
  88. package/public/src/views/dedupe.js.map +1 -0
  89. package/public/src/views/dedupe.ts +43 -0
  90. package/public/src/views/export.js +300 -0
  91. package/public/src/views/export.js.map +1 -0
  92. package/public/src/views/export.ts +274 -0
  93. package/public/src/views/github.js +360 -0
  94. package/public/src/views/github.js.map +1 -0
  95. package/public/src/views/github.ts +308 -0
  96. package/public/src/views/graph-canvas.js +1986 -0
  97. package/public/src/views/graph-canvas.js.map +1 -0
  98. package/public/src/views/graph-canvas.ts +2218 -0
  99. package/public/src/views/graph.js +1824 -0
  100. package/public/src/views/graph.js.map +1 -0
  101. package/public/src/views/graph.ts +1891 -0
  102. package/public/src/views/groups.js +186 -0
  103. package/public/src/views/groups.js.map +1 -0
  104. package/public/src/views/groups.ts +172 -0
  105. package/public/src/views/guide.js +151 -0
  106. package/public/src/views/guide.js.map +1 -0
  107. package/public/src/views/guide.ts +162 -0
  108. package/public/src/views/health.js +105 -0
  109. package/public/src/views/health.js.map +1 -0
  110. package/public/src/views/health.ts +102 -0
  111. package/public/src/views/items.js +1306 -0
  112. package/public/src/views/items.js.map +1 -0
  113. package/public/src/views/items.ts +1196 -0
  114. package/public/src/views/normalize.js +67 -0
  115. package/public/src/views/normalize.js.map +1 -0
  116. package/public/src/views/normalize.ts +58 -0
  117. package/public/src/views/plan.js +454 -0
  118. package/public/src/views/plan.js.map +1 -0
  119. package/public/src/views/plan.ts +496 -0
  120. package/public/src/views/projects.js +204 -0
  121. package/public/src/views/projects.js.map +1 -0
  122. package/public/src/views/projects.ts +196 -0
  123. package/public/src/views/router.js +227 -0
  124. package/public/src/views/router.js.map +1 -0
  125. package/public/src/views/router.ts +188 -0
  126. package/public/src/views/search.js +103 -0
  127. package/public/src/views/search.js.map +1 -0
  128. package/public/src/views/search.ts +94 -0
  129. package/public/src/views/settings.js +272 -0
  130. package/public/src/views/settings.js.map +1 -0
  131. package/public/src/views/settings.ts +190 -0
  132. package/public/src/views/shared.js +49 -0
  133. package/public/src/views/shared.js.map +1 -0
  134. package/public/src/views/shared.ts +49 -0
  135. package/public/src/views/sharing.js +152 -0
  136. package/public/src/views/sharing.js.map +1 -0
  137. package/public/src/views/sharing.ts +139 -0
  138. package/public/src/views/stats.js +92 -0
  139. package/public/src/views/stats.js.map +1 -0
  140. package/public/src/views/stats.ts +88 -0
  141. package/public/src/views/templates.js +117 -0
  142. package/public/src/views/templates.js.map +1 -0
  143. package/public/src/views/templates.ts +113 -0
  144. package/public/src/views/validate.js +54 -0
  145. package/public/src/views/validate.js.map +1 -0
  146. package/public/src/views/validate.ts +48 -0
  147. package/public/styles.css +2231 -0
  148. package/public/sw.js +318 -0
  149. package/public/tsconfig.json +20 -0
  150. package/sql/schema.sql +105 -0
@@ -0,0 +1,265 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover, maximum-scale=1.0, user-scalable=no">
6
+ <meta name="theme-color" content="#0a0f1e">
7
+ <meta name="description" content="pm-web — browser UI for pm-cli project management">
8
+ <meta name="mobile-web-app-capable" content="yes">
9
+ <meta name="apple-mobile-web-app-capable" content="yes">
10
+ <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
11
+ <meta name="apple-mobile-web-app-title" content="pm-web">
12
+ <title>pm-web — Project Management</title>
13
+ <link rel="manifest" href="/manifest.json">
14
+ <link rel="apple-touch-icon" href="/icons/icon-192.png">
15
+ <link rel="apple-touch-icon" sizes="512x512" href="/icons/icon-512.png">
16
+ <link rel="preconnect" href="https://fonts.googleapis.com">
17
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
18
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
19
+ <link rel="stylesheet" href="/styles.css?v=19">
20
+ </head>
21
+ <body>
22
+ <a href="#main-content" class="skip-link">Skip to main content</a>
23
+ <div id="app">
24
+ <!-- Auth Screen -->
25
+ <div id="auth-screen" style="display:none">
26
+ <div class="auth-left">
27
+ <div class="auth-brand">
28
+ <div class="auth-logo">pm<span>-web</span></div>
29
+ <div class="auth-tagline">Git-native project management</div>
30
+ <ul class="auth-features">
31
+ <li>Manage tasks, features, bugs, epics &amp; more</li>
32
+ <li>Git-native storage in .agents/pm/</li>
33
+ <li>Full history, comments &amp; activity feeds</li>
34
+ <li>Team-friendly project isolation</li>
35
+ </ul>
36
+ </div>
37
+ </div>
38
+ <div class="auth-right">
39
+ <div class="auth-form-title" id="auth-title">Welcome back</div>
40
+ <div class="auth-form-sub" id="auth-sub">Sign in to your account to continue</div>
41
+ <div class="auth-toggle">
42
+ <button class="active" id="tab-login" onclick="window.__app.switchAuthTab('login')">Sign In</button>
43
+ <button id="tab-register" onclick="window.__app.switchAuthTab('register')">Register</button>
44
+ </div>
45
+ <form id="auth-form" onsubmit="window.__app.submitAuth(event)">
46
+ <div class="form-group" id="field-name" style="display:none">
47
+ <label class="form-label">Display Name</label>
48
+ <input class="form-input" type="text" id="auth-name" placeholder="Your name" autocomplete="name">
49
+ </div>
50
+ <div class="form-group">
51
+ <label class="form-label">Email</label>
52
+ <input class="form-input" type="email" id="auth-email" placeholder="you@example.com" required autocomplete="email">
53
+ </div>
54
+ <div class="form-group">
55
+ <label class="form-label">Password</label>
56
+ <input class="form-input" type="password" id="auth-password" placeholder="••••••••" required autocomplete="current-password">
57
+ </div>
58
+ <div class="form-error" id="auth-error" style="display:none"></div>
59
+ <button type="submit" class="btn btn-primary btn-full btn-lg" style="margin-top:8px" id="auth-submit">
60
+ <span id="auth-btn-text">Sign In</span>
61
+ </button>
62
+ </form>
63
+ </div>
64
+ </div>
65
+
66
+ <!-- Main App -->
67
+ <div id="main-app" style="display:none">
68
+ <nav id="top-nav" role="navigation" aria-label="Main navigation">
69
+ <div class="nav-logo">pm<span>-web</span></div>
70
+ <div class="nav-divider"></div>
71
+ <div class="project-selector-wrap">
72
+ <label for="project-selector" class="sr-only">Select project</label>
73
+ <select class="project-selector" id="project-selector" onchange="window.__app.onProjectSelect(this.value)" aria-label="Select project">
74
+ <option value="">— Select project —</option>
75
+ </select>
76
+ </div>
77
+ <div class="nav-spacer"></div>
78
+ <div class="nav-search-hint" onclick="window.__app.openSearchModal()" title="Ctrl+K" role="button" tabindex="0" aria-label="Open search (Ctrl+K)" onkeydown="if(event.key==='Enter')window.__app.openSearchModal()">⌘K Search</div>
79
+ <div class="presence-bar" id="presence-bar" style="display:none" aria-label="Users viewing this project"></div>
80
+ <div class="sse-indicator" id="sse-indicator" title="Real-time sync disconnected">
81
+ <div class="sse-dot"></div>
82
+ </div>
83
+ <div class="user-menu">
84
+ <div class="user-avatar" id="user-avatar" aria-hidden="true">?</div>
85
+ <span class="user-name" id="user-name-display"></span>
86
+ <button class="btn btn-ghost btn-sm" onclick="window.__app.showView('settings')" style="margin-right:4px" aria-label="Open settings">Settings</button>
87
+ <button class="btn btn-ghost btn-sm" onclick="window.__app.logout()" aria-label="Sign out">Sign out</button>
88
+ </div>
89
+ </nav>
90
+ <div id="app-body">
91
+ <nav id="sidebar" role="navigation" aria-label="Sidebar navigation">
92
+ <div class="sidebar-section" id="sidebar-projects-section">
93
+ <div class="sidebar-section-label">Projects</div>
94
+ <div class="sidebar-item active" onclick="window.__app.showView('projects')" data-view="projects" role="button" tabindex="0" aria-label="All Projects" onkeydown="if(event.key==='Enter')window.__app.showView('projects')">
95
+ <span class="sidebar-item-icon" aria-hidden="true">⊞</span> All Projects
96
+ </div>
97
+ <div class="sidebar-item" onclick="window.__app.showModal('create-project-modal')" style="color:var(--accent)" role="button" tabindex="0" aria-label="Create new project" onkeydown="if(event.key==='Enter')window.__app.showModal('create-project-modal')">
98
+ <span class="sidebar-item-icon" aria-hidden="true">+</span> New Project
99
+ </div>
100
+ <div class="sidebar-item" onclick="window.__app.showView('settings')" data-view="settings" role="button" tabindex="0" aria-label="Settings" onkeydown="if(event.key==='Enter')window.__app.showView('settings')">
101
+ <span class="sidebar-item-icon" aria-hidden="true">⚙</span> Settings
102
+ </div>
103
+ <div class="sidebar-item admin-only" onclick="window.__app.showView('admin')" data-view="admin" style="display:none" role="button" tabindex="0" aria-label="Admin panel" onkeydown="if(event.key==='Enter')window.__app.showView('admin')">
104
+ <span class="sidebar-item-icon" aria-hidden="true">◇</span> Admin
105
+ </div>
106
+ </div>
107
+ <div class="sidebar-section" id="sidebar-pm-section" style="display:none">
108
+ <div class="sidebar-section-label" id="sidebar-project-name">Project</div>
109
+ <div class="sidebar-item" onclick="window.__app.showView('items')" data-view="items" role="button" tabindex="0" aria-label="Items" onkeydown="if(event.key==='Enter')window.__app.showView('items')">
110
+ <span class="sidebar-item-icon" aria-hidden="true">≡</span> Items
111
+ <span class="sidebar-badge" id="badge-items"></span>
112
+ </div>
113
+ <div class="sidebar-item" onclick="window.__app.showView('create')" data-view="create" role="button" tabindex="0" aria-label="Create item" onkeydown="if(event.key==='Enter')window.__app.showView('create')">
114
+ <span class="sidebar-item-icon" aria-hidden="true">+</span> Create Item
115
+ </div>
116
+ <div class="sidebar-item" onclick="window.__app.showView('plan')" data-view="plan" role="button" tabindex="0" aria-label="Plans" onkeydown="if(event.key==='Enter')window.__app.showView('plan')">
117
+ <span class="sidebar-item-icon" aria-hidden="true">◧</span> Plans
118
+ </div>
119
+ <div class="sidebar-item" onclick="window.__app.showView('activity')" data-view="activity" role="button" tabindex="0" aria-label="Activity" onkeydown="if(event.key==='Enter')window.__app.showView('activity')">
120
+ <span class="sidebar-item-icon" aria-hidden="true">◎</span> Activity
121
+ </div>
122
+ <div class="sidebar-item" onclick="window.__app.showView('search')" data-view="search" role="button" tabindex="0" aria-label="Search" onkeydown="if(event.key==='Enter')window.__app.showView('search')">
123
+ <span class="sidebar-item-icon" aria-hidden="true">⌕</span> Search
124
+ </div>
125
+ <div class="sidebar-item" onclick="window.__app.showView('stats')" data-view="stats" role="button" tabindex="0" aria-label="Stats" onkeydown="if(event.key==='Enter')window.__app.showView('stats')">
126
+ <span class="sidebar-item-icon" aria-hidden="true">◈</span> Stats
127
+ </div>
128
+ <div class="sidebar-item" onclick="window.__app.showView('calendar')" data-view="calendar" role="button" tabindex="0" aria-label="Calendar" onkeydown="if(event.key==='Enter')window.__app.showView('calendar')">
129
+ <span class="sidebar-item-icon" aria-hidden="true">◷</span> Calendar
130
+ </div>
131
+ <div class="sidebar-item" onclick="window.__app.showView('context')" data-view="context" role="button" tabindex="0" aria-label="Context" onkeydown="if(event.key==='Enter')window.__app.showView('context')">
132
+ <span class="sidebar-item-icon" aria-hidden="true">⚙</span> Context
133
+ </div>
134
+ <div class="sidebar-item" onclick="window.__app.showView('graph')" data-view="graph" role="button" tabindex="0" aria-label="Graph" onkeydown="if(event.key==='Enter')window.__app.showView('graph')">
135
+ <span class="sidebar-item-icon" aria-hidden="true">◎</span> Graph
136
+ </div>
137
+ <div class="sidebar-item" onclick="window.__app.showView('config')" data-view="config" role="button" tabindex="0" aria-label="Config" onkeydown="if(event.key==='Enter')window.__app.showView('config')">
138
+ <span class="sidebar-item-icon" aria-hidden="true">⚒</span> Config
139
+ </div>
140
+ <div class="sidebar-item" onclick="window.__app.showView('sharing')" data-view="sharing" role="button" tabindex="0" aria-label="Sharing" onkeydown="if(event.key==='Enter')window.__app.showView('sharing')">
141
+ <span class="sidebar-item-icon" aria-hidden="true">⇄</span> Sharing
142
+ </div>
143
+ <div class="sidebar-item" onclick="window.__app.showView('health')" data-view="health" role="button" tabindex="0" aria-label="Health" onkeydown="if(event.key==='Enter')window.__app.showView('health')">
144
+ <span class="sidebar-item-icon" aria-hidden="true">♥</span> Health
145
+ </div>
146
+ <div class="sidebar-item" onclick="window.__app.showView('dedupe')" data-view="dedupe" role="button" tabindex="0" aria-label="Dedupe audit" onkeydown="if(event.key==='Enter')window.__app.showView('dedupe')">
147
+ <span class="sidebar-item-icon" aria-hidden="true">⧖</span> Dedupe Audit
148
+ </div>
149
+ <div class="sidebar-item" onclick="window.__app.showView('validate')" data-view="validate" role="button" tabindex="0" aria-label="Validate" onkeydown="if(event.key==='Enter')window.__app.showView('validate')">
150
+ <span class="sidebar-item-icon" aria-hidden="true">✓</span> Validate
151
+ </div>
152
+ <div class="sidebar-item" onclick="window.__app.showView('github')" data-view="github" role="button" tabindex="0" aria-label="GitHub" onkeydown="if(event.key==='Enter')window.__app.showView('github')">
153
+ <span class="sidebar-item-icon" aria-hidden="true">⊙</span> GitHub
154
+ </div>
155
+ <div class="sidebar-item" onclick="window.__app.showView('export')" data-view="export" role="button" tabindex="0" aria-label="Export or import" onkeydown="if(event.key==='Enter')window.__app.showView('export')">
156
+ <span class="sidebar-item-icon" aria-hidden="true">↕</span> Export / Import
157
+ </div>
158
+ <div class="sidebar-item" onclick="window.__app.showView('normalize')" data-view="normalize" role="button" tabindex="0" aria-label="Normalize" onkeydown="if(event.key==='Enter')window.__app.showView('normalize')">
159
+ <span class="sidebar-item-icon" aria-hidden="true">⊞</span> Normalize
160
+ </div>
161
+ <div class="sidebar-item" onclick="window.__app.showView('templates')" data-view="templates" role="button" tabindex="0" aria-label="Templates" onkeydown="if(event.key==='Enter')window.__app.showView('templates')">
162
+ <span class="sidebar-item-icon" aria-hidden="true">⎘</span> Templates
163
+ </div>
164
+ <div class="sidebar-item" onclick="window.__app.showView('guide')" data-view="guide" role="button" tabindex="0" aria-label="Guide" onkeydown="if(event.key==='Enter')window.__app.showView('guide')">
165
+ <span class="sidebar-item-icon" aria-hidden="true">📖</span> Guide
166
+ </div>
167
+ </div>
168
+ <div class="sidebar-section" id="sidebar-logs-section">
169
+ <div class="sidebar-section-label">Logs</div>
170
+ <div class="sidebar-item" onclick="window.__app.showView('comments-audit')" data-view="comments-audit" role="button" tabindex="0" aria-label="Comments audit" onkeydown="if(event.key==='Enter')window.__app.showView('comments-audit')">
171
+ <span class="sidebar-item-icon" aria-hidden="true">💬</span> Comments Audit
172
+ </div>
173
+ </div>
174
+ <div class="sidebar-section">
175
+ <div class="sidebar-section-label">Team</div>
176
+ <div class="sidebar-item" onclick="window.__app.showView('groups')" data-view="groups" role="button" tabindex="0" aria-label="Groups" onkeydown="if(event.key==='Enter')window.__app.showView('groups')">
177
+ <span class="sidebar-item-icon" aria-hidden="true">◉</span> Groups
178
+ </div>
179
+ <div class="sidebar-item" onclick="window.__app.showView('shared')" data-view="shared" role="button" tabindex="0" aria-label="Shared with me" onkeydown="if(event.key==='Enter')window.__app.showView('shared')">
180
+ <span class="sidebar-item-icon" aria-hidden="true">⇄</span> Shared with Me
181
+ </div>
182
+ </div>
183
+ </nav>
184
+ <main id="main-content" role="main">
185
+ <div class="content-area" id="content-projects"><!-- projects view --></div>
186
+ <div class="content-area" id="content-items" style="display:none"><!-- items --></div>
187
+ <div class="content-area" id="content-create" style="display:none"><!-- create --></div>
188
+ <div class="content-area" id="content-activity" style="display:none"><!-- activity --></div>
189
+ <div class="content-area" id="content-search" style="display:none"><!-- search --></div>
190
+ <div class="content-area" id="content-stats" style="display:none"><!-- stats --></div>
191
+ <div class="content-area" id="content-calendar" style="display:none"><!-- calendar --></div>
192
+ <div class="content-area" id="content-context" style="display:none"><!-- context --></div>
193
+ <div class="content-area" id="content-graph" style="display:none"><!-- graph --></div>
194
+ <div class="content-area" id="content-sharing" style="display:none"><!-- sharing --></div>
195
+ <div class="content-area" id="content-groups" style="display:none"><!-- groups --></div>
196
+ <div class="content-area" id="content-health" style="display:none"><!-- health --></div>
197
+ <div class="content-area" id="content-dedupe" style="display:none"><!-- dedupe --></div>
198
+ <div class="content-area" id="content-validate" style="display:none"><!-- validate --></div>
199
+ <div class="content-area" id="content-settings" style="display:none"><!-- settings --></div>
200
+ <div class="content-area" id="content-github" style="display:none"><!-- github --></div>
201
+ <div class="content-area" id="content-export" style="display:none"><!-- export/import --></div>
202
+ <div class="content-area" id="content-normalize" style="display:none"><!-- normalize --></div>
203
+ <div class="content-area" id="content-shared" style="display:none"><!-- shared with me --></div>
204
+ <div class="content-area" id="content-templates" style="display:none"><!-- templates --></div>
205
+ <div class="content-area" id="content-comments-audit" style="display:none"><!-- comments audit --></div>
206
+ <div class="content-area" id="content-config" style="display:none"><!-- project config --></div>
207
+ <div class="content-area" id="content-guide" style="display:none"><!-- guide --></div>
208
+ <div class="content-area" id="content-admin" style="display:none"><!-- admin --></div>
209
+ <div class="content-area" id="content-plan" style="display:none"><!-- plan --></div>
210
+ </main>
211
+ </div>
212
+ </div>
213
+ </div>
214
+
215
+ <!-- Modals -->
216
+ <div id="modal-container"></div>
217
+ <div id="toast-container"></div>
218
+
219
+ <!-- Offline Banner -->
220
+ <div class="offline-banner" id="offline-banner">
221
+ <span>You are offline — changes will sync when reconnected</span>
222
+ </div>
223
+
224
+ <!-- PWA Install Banner -->
225
+ <div class="install-banner" id="install-banner">
226
+ <div class="install-banner-text">Install <strong>pm-web</strong> for a native app experience</div>
227
+ <div style="display:flex;gap:8px">
228
+ <button class="btn btn-ghost btn-sm" onclick="dismissInstallBanner()">Not now</button>
229
+ <button class="btn btn-primary btn-sm" id="install-btn" onclick="installPwa()"><span>Install</span></button>
230
+ </div>
231
+ </div>
232
+
233
+ <!-- Mobile Bottom Nav (PWA) -->
234
+ <nav class="mobile-bottom-nav" id="mobile-bottom-nav">
235
+ <div class="mobile-bottom-nav-inner">
236
+ <div class="mobile-bottom-nav-item" onclick="window.__app.showView('items')" data-mobview="items" role="button" tabindex="0" aria-label="Items" onkeydown="if(event.key==='Enter')window.__app.showView('items')">
237
+ <div class="mobile-bottom-nav-item-icon" aria-hidden="true">≡</div>
238
+ <div class="mobile-bottom-nav-item-label">Items</div>
239
+ </div>
240
+ <div class="mobile-bottom-nav-item" onclick="window.__app.showView('create')" data-mobview="create" role="button" tabindex="0" aria-label="Create item" onkeydown="if(event.key==='Enter')window.__app.showView('create')">
241
+ <div class="mobile-bottom-nav-item-icon" aria-hidden="true">+</div>
242
+ <div class="mobile-bottom-nav-item-label">Create</div>
243
+ </div>
244
+ <div class="mobile-bottom-nav-item" onclick="window.__app.openSearchModal()" data-mobview="search" role="button" tabindex="0" aria-label="Search" onkeydown="if(event.key==='Enter')window.__app.openSearchModal()">
245
+ <div class="mobile-bottom-nav-item-icon" aria-hidden="true">⌕</div>
246
+ <div class="mobile-bottom-nav-item-label">Search</div>
247
+ </div>
248
+ <div class="mobile-bottom-nav-item" onclick="window.__app.showView('stats')" data-mobview="stats" role="button" tabindex="0" aria-label="Stats" onkeydown="if(event.key==='Enter')window.__app.showView('stats')">
249
+ <div class="mobile-bottom-nav-item-icon" aria-hidden="true">◈</div>
250
+ <div class="mobile-bottom-nav-item-label">Stats</div>
251
+ </div>
252
+ <div class="mobile-bottom-nav-item" onclick="window.__app.showView('shared')" data-mobview="shared" role="button" tabindex="0" aria-label="Shared" onkeydown="if(event.key==='Enter')window.__app.showView('shared')">
253
+ <div class="mobile-bottom-nav-item-icon" aria-hidden="true">⇄</div>
254
+ <div class="mobile-bottom-nav-item-label">Shared</div>
255
+ </div>
256
+ <div class="mobile-bottom-nav-item" onclick="window.__app.openMobileCommandSheet()" data-mobview="more" role="button" tabindex="0" aria-label="More options" onkeydown="if(event.key==='Enter')window.__app.openMobileCommandSheet()">
257
+ <div class="mobile-bottom-nav-item-icon" aria-hidden="true">⚙</div>
258
+ <div class="mobile-bottom-nav-item-label">More</div>
259
+ </div>
260
+ </div>
261
+ </nav>
262
+
263
+ <script type="module" src="/src/app.js?v=9"></script>
264
+ </body>
265
+ </html>
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "pm-web — Project Management",
3
+ "short_name": "pm-web",
4
+ "description": "Full-featured web UI for pm-cli — manage projects, tasks, epics, bugs, and more from any device",
5
+ "start_url": "/",
6
+ "display": "standalone",
7
+ "background_color": "#0a0f1e",
8
+ "theme_color": "#2dd4bf",
9
+ "orientation": "any",
10
+ "scope": "/",
11
+ "lang": "en",
12
+ "dir": "ltr",
13
+ "categories": ["productivity", "utilities", "business"],
14
+ "icons": [
15
+ {
16
+ "src": "/icons/icon-192.png",
17
+ "sizes": "192x192",
18
+ "type": "image/png",
19
+ "purpose": "any"
20
+ },
21
+ {
22
+ "src": "/icons/icon-192.png",
23
+ "sizes": "192x192",
24
+ "type": "image/png",
25
+ "purpose": "maskable"
26
+ },
27
+ {
28
+ "src": "/icons/icon-512.png",
29
+ "sizes": "512x512",
30
+ "type": "image/png",
31
+ "purpose": "any"
32
+ },
33
+ {
34
+ "src": "/icons/icon-512.png",
35
+ "sizes": "512x512",
36
+ "type": "image/png",
37
+ "purpose": "maskable"
38
+ }
39
+ ],
40
+ "shortcuts": [
41
+ {
42
+ "name": "New Item",
43
+ "url": "/?action=new-item",
44
+ "description": "Create a new pm item",
45
+ "icons": [{"src": "/icons/icon-192.png", "sizes": "192x192"}]
46
+ },
47
+ {
48
+ "name": "Search",
49
+ "url": "/?action=search",
50
+ "description": "Search across all items",
51
+ "icons": [{"src": "/icons/icon-192.png", "sizes": "192x192"}]
52
+ },
53
+ {
54
+ "name": "New Project",
55
+ "url": "/?action=new-project",
56
+ "description": "Create a new pm project",
57
+ "icons": [{"src": "/icons/icon-192.png", "sizes": "192x192"}]
58
+ }
59
+ ],
60
+ "screenshots": [],
61
+ "prefer_related_applications": false,
62
+ "display_override": ["window-controls-overlay", "standalone", "minimal-ui"],
63
+ "edge_side_panel": {
64
+ "preferred_width": 400
65
+ }
66
+ }
@@ -0,0 +1,28 @@
1
+ // ═══════════════════════════════════════════════════════════════
2
+ // API CLIENT
3
+ // ═══════════════════════════════════════════════════════════════
4
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
5
+ export async function api(method, path, body) {
6
+ const opts = {
7
+ method,
8
+ headers: { 'Content-Type': 'application/json' },
9
+ credentials: 'include',
10
+ };
11
+ if (body !== undefined)
12
+ opts.body = JSON.stringify(body);
13
+ const res = await fetch('/api' + path, opts);
14
+ const data = await res.json().catch(() => ({}));
15
+ if (!res.ok) {
16
+ throw new Error(String(data.error || `HTTP ${res.status}`));
17
+ }
18
+ return data;
19
+ }
20
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
21
+ export async function getGuide(projectId) {
22
+ return api('GET', `/projects/${projectId}/pm/guide`);
23
+ }
24
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
25
+ export async function getGuideTopic(projectId, topicId) {
26
+ return api('GET', `/projects/${projectId}/pm/guide/${encodeURIComponent(topicId)}`);
27
+ }
28
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["api.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,aAAa;AACb,kEAAkE;AAElE,8DAA8D;AAC9D,MAAM,CAAC,KAAK,UAAU,GAAG,CAAC,MAAc,EAAE,IAAY,EAAE,IAAc;IACpE,MAAM,IAAI,GAAgB;QACxB,MAAM;QACN,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC/C,WAAW,EAAE,SAAS;KACvB,CAAC;IACF,IAAI,IAAI,KAAK,SAAS;QAAE,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IACzD,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,MAAM,GAAG,IAAI,EAAE,IAAI,CAAC,CAAC;IAC7C,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAA4B,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACzE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,IAAI,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,8DAA8D;AAC9D,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,SAAiB;IAC9C,OAAO,GAAG,CAAC,KAAK,EAAE,aAAa,SAAS,WAAW,CAAC,CAAC;AACvD,CAAC;AAED,8DAA8D;AAC9D,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,SAAiB,EAAE,OAAe;IACpE,OAAO,GAAG,CAAC,KAAK,EAAE,aAAa,SAAS,aAAa,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;AACtF,CAAC"}
@@ -0,0 +1,29 @@
1
+ // ═══════════════════════════════════════════════════════════════
2
+ // API CLIENT
3
+ // ═══════════════════════════════════════════════════════════════
4
+
5
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6
+ export async function api(method: string, path: string, body?: unknown): Promise<any> {
7
+ const opts: RequestInit = {
8
+ method,
9
+ headers: { 'Content-Type': 'application/json' },
10
+ credentials: 'include',
11
+ };
12
+ if (body !== undefined) opts.body = JSON.stringify(body);
13
+ const res = await fetch('/api' + path, opts);
14
+ const data = await res.json().catch((): Record<string, unknown> => ({}));
15
+ if (!res.ok) {
16
+ throw new Error(String(data.error || `HTTP ${res.status}`));
17
+ }
18
+ return data;
19
+ }
20
+
21
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
22
+ export async function getGuide(projectId: string): Promise<any> {
23
+ return api('GET', `/projects/${projectId}/pm/guide`);
24
+ }
25
+
26
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
27
+ export async function getGuideTopic(projectId: string, topicId: string): Promise<any> {
28
+ return api('GET', `/projects/${projectId}/pm/guide/${encodeURIComponent(topicId)}`);
29
+ }