@sansynx/erroratlas 0.1.1 → 0.1.3

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/README.md CHANGED
@@ -4,6 +4,36 @@ ErrorAtlas is hosted debugging memory for coding agents.
4
4
 
5
5
  Developers do not clone this repo, create Supabase projects, deploy Workers, or manage ErrorAtlas infrastructure. They install the npm MCP client, generate one hosted MCP key, run Supermemory locally, and let their coding agent search and publish verified fixes.
6
6
 
7
+ ## Working flow
8
+
9
+ ```mermaid
10
+ flowchart TD
11
+ Dev[Developer] --> Console[Hosted ErrorAtlas dashboard]
12
+ Console --> Key[Generate one MCP key]
13
+
14
+ Dev --> Agent[Codex / Cursor / Windsurf / OpenCode]
15
+ Agent --> MCP[Local ErrorAtlas MCP server]
16
+
17
+ MCP --> LocalMemory[Supermemory local]
18
+ MCP --> API[Hosted ErrorAtlas API]
19
+
20
+ LocalMemory --> PrivateContext[Private project memory stays on machine]
21
+ API --> Search[Search sanitized shared fixes]
22
+ API --> Store[Store sanitized incidents and verified playbooks]
23
+
24
+ Search --> Agent
25
+ PrivateContext --> Agent
26
+ ```
27
+
28
+ ```txt
29
+ 1. Developer signs in at the hosted dashboard.
30
+ 2. Dashboard generates one MCP key.
31
+ 3. Developer installs @sansynx/erroratlas in their project.
32
+ 4. Coding agent connects to the local ErrorAtlas MCP server.
33
+ 5. MCP searches Supermemory local plus hosted ErrorAtlas fixes.
34
+ 6. After a verified fix, MCP stores sanitized incidents and playbooks remotely.
35
+ ```
36
+
7
37
  ## End-user flow
8
38
 
9
39
  1. Open the hosted dashboard:
@@ -89,70 +119,3 @@ erroratlas.get_playbook
89
119
  - ErrorAtlas remote stores sanitized searchable incidents and playbooks.
90
120
  - Agent keys are shown once and stored remotely only as hashes.
91
121
  - Sensitive incident payload snapshots are encrypted before database insert.
92
-
93
- ## Operator setup
94
-
95
- This section is only for the ErrorAtlas service owner.
96
-
97
- ```bash
98
- cp .dev.vars.example .dev.vars
99
- npm install
100
- npm run dev
101
- ```
102
-
103
- Apply the database schema from:
104
-
105
- ```txt
106
- supabase/schema.sql
107
- ```
108
-
109
- Set production Worker secrets:
110
-
111
- ```bash
112
- npx wrangler secret put PUBLIC_SUPABASE_URL
113
- npx wrangler secret put PUBLIC_SUPABASE_ANON_KEY
114
- npx wrangler secret put SUPABASE_SERVICE_ROLE_KEY
115
- npx wrangler secret put FIELD_ENCRYPTION_KEY
116
- ```
117
-
118
- Deploy:
119
-
120
- ```bash
121
- npm run deploy
122
- ```
123
-
124
- Publish the npm package from an npm account or organization you control:
125
-
126
- ```bash
127
- npm login
128
- npm publish --access public
129
- ```
130
-
131
- The package name is `@sansynx/erroratlas` because the unscoped npm name `erroratlas` is already owned by another package. The CLI command remains `erroratlas`.
132
-
133
- ## Project structure
134
-
135
- ```txt
136
- src/worker Cloudflare Worker UI and API
137
- src/mcp local MCP server for coding agents
138
- src/cli npm package CLI for init and MCP startup
139
- src/shared redaction and playbook utilities
140
- supabase service-owner database schema
141
- templates files copied by npx erroratlas init
142
- public Worker-served public assets
143
- scripts maintainer build and smoke-test scripts
144
- ```
145
-
146
- ## Important routes
147
-
148
- ```txt
149
- GET / landing page
150
- GET /setup public setup guide
151
- GET /dashboard protected MCP key console
152
- GET /llms.txt machine-readable agent guide
153
- GET /api/health service health
154
- POST /api/search search approved fixes
155
- POST /api/agent-keys create or rotate MCP key
156
- POST /api/incidents capture sanitized error signal
157
- POST /api/resolutions publish verified resolution
158
- ```
@@ -62,21 +62,12 @@ async function getOrCreateHumanActor(env, user) {
62
62
  }
63
63
  const slug = `personal-${user.id.slice(0, 8)}`;
64
64
  const orgName = "Personal atlas";
65
- const org = await supabaseRest(env, "organizations", {
66
- method: "POST",
67
- body: JSON.stringify([{ name: orgName, slug }])
68
- });
69
- const orgId = org[0]?.id;
70
- if (!orgId)
71
- throw new HttpError(500, "org_create_failed", "Could not create organization.");
65
+ const orgId = await ensurePersonalOrg(env, slug, orgName);
72
66
  await supabaseRest(env, "organization_members", {
73
67
  method: "POST",
74
68
  body: JSON.stringify([{ org_id: orgId, user_id: user.id, role: "owner" }])
75
69
  });
76
- await supabaseRest(env, "projects", {
77
- method: "POST",
78
- body: JSON.stringify([{ org_id: orgId, name: "Default", slug: "default", visibility: "team" }])
79
- });
70
+ await ensureDefaultProject(env, orgId);
80
71
  return {
81
72
  kind: "human",
82
73
  userId: user.id,
@@ -87,6 +78,40 @@ async function getOrCreateHumanActor(env, user) {
87
78
  role: "owner"
88
79
  };
89
80
  }
81
+ async function ensurePersonalOrg(env, slug, orgName) {
82
+ try {
83
+ const org = await supabaseRest(env, "organizations", {
84
+ method: "POST",
85
+ body: JSON.stringify([{ name: orgName, slug }])
86
+ });
87
+ const orgId = org[0]?.id;
88
+ if (!orgId)
89
+ throw new HttpError(500, "org_create_failed", "Could not create workspace.");
90
+ return orgId;
91
+ }
92
+ catch (error) {
93
+ if (!(error instanceof HttpError && /duplicate|unique|23505|409/i.test(error.message)))
94
+ throw error;
95
+ const existing = await supabaseRest(env, `organizations?select=id&slug=eq.${encodeURIComponent(slug)}&limit=1`);
96
+ const orgId = existing[0]?.id;
97
+ if (!orgId)
98
+ throw error;
99
+ return orgId;
100
+ }
101
+ }
102
+ async function ensureDefaultProject(env, orgId) {
103
+ try {
104
+ await supabaseRest(env, "projects", {
105
+ method: "POST",
106
+ body: JSON.stringify([{ org_id: orgId, name: "Default", slug: "default", visibility: "team" }])
107
+ });
108
+ }
109
+ catch (error) {
110
+ if (error instanceof HttpError && /duplicate|unique|23505|409/i.test(error.message))
111
+ return;
112
+ throw error;
113
+ }
114
+ }
90
115
  async function ensureUserProfile(env, user) {
91
116
  const existing = await supabaseRest(env, `user_profiles?select=user_id,username,display_name,bio&user_id=eq.${encodeURIComponent(user.id)}&limit=1`);
92
117
  if (existing[0])
package/dist/worker/ui.js CHANGED
@@ -369,6 +369,9 @@ button { cursor: pointer; }
369
369
  .nav-links { display: flex; align-items: center; gap: 10px; color: var(--body); }
370
370
  .nav-links a,
371
371
  .nav-links button { display: inline-flex; align-items: center; justify-content: center; }
372
+ .nav-actions {
373
+ display: contents;
374
+ }
372
375
  .nav-menu {
373
376
  display: none;
374
377
  min-height: 38px;
@@ -674,7 +677,6 @@ ul, ol { margin: 0; padding-left: 20px; color: var(--body); }
674
677
  }
675
678
  .nav-menu {
676
679
  display: inline-flex;
677
- justify-self: end;
678
680
  width: 40px;
679
681
  height: 40px;
680
682
  padding: 0;
@@ -712,6 +714,21 @@ ul, ol { margin: 0; padding-left: 20px; color: var(--body); }
712
714
  transform: translateY(-4px);
713
715
  transition: max-height 240ms ease, opacity 180ms ease, transform 220ms ease, margin 220ms ease, padding 220ms ease, border-color 180ms ease;
714
716
  }
717
+ .nav-actions {
718
+ display: flex;
719
+ justify-self: end;
720
+ align-items: center;
721
+ gap: 4px;
722
+ }
723
+ .nav-actions .theme-toggle {
724
+ display: inline-flex;
725
+ width: 38px;
726
+ min-width: 38px;
727
+ height: 38px;
728
+ min-height: 38px;
729
+ border: 0;
730
+ background: transparent;
731
+ }
715
732
  .nav.open .nav-links {
716
733
  max-height: 260px;
717
734
  margin: 6px 0 0;
@@ -722,7 +739,6 @@ ul, ol { margin: 0; padding-left: 20px; color: var(--body); }
722
739
  transform: translateY(0);
723
740
  }
724
741
  .nav-links .button,
725
- .nav-links .theme-toggle,
726
742
  .nav-links > a:not(.button) {
727
743
  width: 100%;
728
744
  min-height: 40px;
@@ -812,33 +828,178 @@ ul, ol { margin: 0; padding-left: 20px; color: var(--body); }
812
828
  padding-left: 12px;
813
829
  transform: none;
814
830
  }
815
- .nav.open .theme-toggle {
816
- width: 100%;
817
- height: 46px;
818
- min-height: 46px;
819
- min-width: 0;
820
- justify-content: flex-start;
821
- gap: 10px;
822
- padding: 0 4px;
823
- border: 0;
824
- border-bottom: 1px solid var(--line);
831
+ .nav.open .nav-links > *:last-child {
832
+ border-bottom: 0;
833
+ }
834
+ }
835
+
836
+ .nav {
837
+ min-height: 64px;
838
+ margin-top: 18px;
839
+ padding: 10px 12px;
840
+ border: 1px solid var(--line);
841
+ border-radius: 20px;
842
+ background:
843
+ linear-gradient(180deg, color-mix(in srgb, var(--surface) 92%, transparent), color-mix(in srgb, var(--surface-2) 78%, transparent));
844
+ box-shadow: 0 14px 48px rgba(0, 0, 0, 0.08);
845
+ }
846
+ .brand {
847
+ gap: 9px;
848
+ padding-left: 4px;
849
+ }
850
+ .brand::before {
851
+ display: none;
852
+ content: none;
853
+ }
854
+ .nav-links {
855
+ gap: 6px;
856
+ }
857
+ .nav-links > a:not(.button),
858
+ .nav-links .button,
859
+ .nav-links .theme-toggle,
860
+ .nav-actions .theme-toggle {
861
+ height: 38px;
862
+ min-height: 38px;
863
+ border-radius: 12px;
864
+ }
865
+ .nav-links > a:not(.button) {
866
+ padding: 0 12px;
867
+ color: var(--body);
868
+ }
869
+ .nav-links > a:not(.button):hover {
870
+ color: var(--fg);
871
+ background: var(--surface-2);
872
+ }
873
+ .theme-toggle {
874
+ border-color: transparent;
875
+ background: var(--surface-2);
876
+ }
877
+ .secret-strip {
878
+ display: grid;
879
+ gap: 14px;
880
+ margin: 18px 0 20px;
881
+ padding: 16px;
882
+ border: 1px solid var(--line);
883
+ border-radius: 16px;
884
+ background: var(--surface-2);
885
+ }
886
+ .secret-strip.is-ready {
887
+ border-color: color-mix(in srgb, var(--good) 45%, var(--line));
888
+ background: color-mix(in srgb, var(--green-100) 48%, var(--surface));
889
+ }
890
+ .secret-strip strong {
891
+ display: block;
892
+ margin-bottom: 4px;
893
+ color: var(--fg);
894
+ }
895
+ .secret-strip span {
896
+ color: var(--muted);
897
+ }
898
+ .secret-copy-row {
899
+ display: grid;
900
+ grid-template-columns: minmax(0, 1fr) auto;
901
+ align-items: center;
902
+ gap: 10px;
903
+ }
904
+ .secret-token {
905
+ display: block;
906
+ min-width: 0;
907
+ padding: 10px 12px;
908
+ overflow: hidden;
909
+ border: 1px solid var(--line);
910
+ border-radius: 10px;
911
+ background: var(--surface);
912
+ color: var(--fg) !important;
913
+ font-family: var(--font-mono);
914
+ font-size: 13px;
915
+ text-overflow: ellipsis;
916
+ white-space: nowrap;
917
+ }
918
+ .secret-copy-row .button {
919
+ height: 40px;
920
+ min-height: 40px;
921
+ border-radius: 10px;
922
+ }
923
+ @media (max-width: 760px) {
924
+ .nav {
925
+ margin-top: 12px;
926
+ padding: 10px 0;
927
+ border-color: transparent;
825
928
  border-radius: 0;
826
929
  background: transparent;
827
- color: var(--fg);
828
930
  box-shadow: none;
829
931
  }
830
- .nav.open .theme-toggle:hover {
831
- background: var(--surface-2);
832
- padding-left: 12px;
833
- transform: none;
932
+ .nav.open {
933
+ padding: 10px 0 0;
934
+ border-radius: 0;
935
+ background: transparent;
936
+ border-color: transparent;
937
+ box-shadow: none;
834
938
  }
835
- .nav.open .theme-toggle::after {
836
- content: "Theme";
837
- font-size: 13px;
838
- font-weight: 500;
939
+ .nav.open .nav-links {
940
+ margin-top: 14px;
941
+ padding: 12px 0 0;
942
+ border-top: 1px solid var(--line);
943
+ background: transparent;
839
944
  }
840
- .nav.open .nav-links > *:last-child {
945
+ .nav.open .nav-links .button,
946
+ .nav.open .nav-links > a:not(.button) {
947
+ justify-content: flex-start;
948
+ height: 44px;
949
+ min-height: 44px;
950
+ padding: 0 10px;
951
+ border-radius: 12px;
952
+ border: 0;
841
953
  border-bottom: 0;
954
+ background: transparent;
955
+ }
956
+ .secret-copy-row {
957
+ grid-template-columns: 1fr;
958
+ }
959
+ .secret-copy-row .button {
960
+ width: 100%;
961
+ }
962
+ }
963
+ .nav-menu {
964
+ background: transparent;
965
+ }
966
+ @media (max-width: 760px) {
967
+ .brand {
968
+ padding-left: 0;
969
+ font-size: 14px;
970
+ height: 40px;
971
+ align-items: center;
972
+ }
973
+ .nav-menu {
974
+ justify-self: end;
975
+ width: 38px;
976
+ min-width: 38px;
977
+ height: 38px;
978
+ background: transparent;
979
+ }
980
+ .nav.open .nav-links {
981
+ gap: 8px;
982
+ }
983
+ .nav.open .nav-menu {
984
+ background: transparent;
985
+ }
986
+ .nav.open .nav-links .button,
987
+ .nav.open .nav-links > a:not(.button) {
988
+ align-items: center;
989
+ justify-content: center;
990
+ padding-left: 0;
991
+ padding-right: 0;
992
+ font-size: 14px;
993
+ line-height: 1;
994
+ text-align: center;
995
+ }
996
+ .landing-page .hero,
997
+ .setup-page .hero {
998
+ justify-items: center;
999
+ text-align: center;
1000
+ }
1001
+ .landing-page .hero {
1002
+ padding-top: 64px;
842
1003
  }
843
1004
  }
844
1005
  </style>`;
@@ -851,6 +1012,7 @@ export function renderLandingUi(env) {
851
1012
  <meta charset="utf-8">
852
1013
  <meta name="viewport" content="width=device-width, initial-scale=1">
853
1014
  <title>${appName}</title>
1015
+ <link rel="icon" href="/favicon.svg" type="image/svg+xml">
854
1016
  ${themeBootScript()}
855
1017
  ${sharedCss()}
856
1018
  <style>
@@ -1017,6 +1179,10 @@ ${sharedCss()}
1017
1179
  }
1018
1180
  .footer {
1019
1181
  flex-direction: column;
1182
+ align-items: center;
1183
+ justify-content: center;
1184
+ text-align: center;
1185
+ width: 100%;
1020
1186
  }
1021
1187
  }
1022
1188
  </style>
@@ -1025,10 +1191,12 @@ ${sharedCss()}
1025
1191
  <div class="shell narrow">
1026
1192
  <nav class="nav">
1027
1193
  <a href="/" class="brand">ErrorAtlas</a>
1028
- <button class="nav-menu" id="navMenu" type="button" aria-expanded="false" aria-controls="navLinks" aria-label="Open navigation"><span class="nav-menu-lines" aria-hidden="true"><span class="nav-menu-line"></span><span class="nav-menu-line"></span><span class="nav-menu-line"></span></span></button>
1194
+ <div class="nav-actions">
1195
+ <button class="theme-toggle" id="themeToggle" type="button" aria-label="Switch theme" title="Switch theme"><span class="theme-toggle-icon" aria-hidden="true"></span></button>
1196
+ <button class="nav-menu" id="navMenu" type="button" aria-expanded="false" aria-controls="navLinks" aria-label="Open navigation"><span class="nav-menu-lines" aria-hidden="true"><span class="nav-menu-line"></span><span class="nav-menu-line"></span><span class="nav-menu-line"></span></span></button>
1197
+ </div>
1029
1198
  <div class="nav-links" id="navLinks">
1030
1199
  <a href="/setup">Setup</a>
1031
- <button class="theme-toggle" id="themeToggle" type="button" aria-label="Switch theme" title="Switch theme"><span class="theme-toggle-icon" aria-hidden="true"></span></button>
1032
1200
  <a class="button primary" href="/dashboard">Dashboard</a>
1033
1201
  </div>
1034
1202
  </nav>
@@ -1087,6 +1255,7 @@ export function renderSetupGuideUi(env, requestOrigin) {
1087
1255
  <meta charset="utf-8">
1088
1256
  <meta name="viewport" content="width=device-width, initial-scale=1">
1089
1257
  <title>${appName} Setup</title>
1258
+ <link rel="icon" href="/favicon.svg" type="image/svg+xml">
1090
1259
  ${themeBootScript()}
1091
1260
  ${sharedCss()}
1092
1261
  <style>
@@ -1195,9 +1364,6 @@ ${sharedCss()}
1195
1364
  font-weight: 600;
1196
1365
  }
1197
1366
  @media (max-width: 760px) {
1198
- .setup-page .nav {
1199
- border-radius: 999px;
1200
- }
1201
1367
  .setup-page .nav-links {
1202
1368
  border-top: 1px solid var(--line);
1203
1369
  }
@@ -1227,10 +1393,12 @@ ${sharedCss()}
1227
1393
  <div class="shell narrow">
1228
1394
  <nav class="nav">
1229
1395
  <a href="/" class="brand">ErrorAtlas</a>
1230
- <button class="nav-menu" id="navMenu" type="button" aria-expanded="false" aria-controls="navLinks" aria-label="Open navigation"><span class="nav-menu-lines" aria-hidden="true"><span class="nav-menu-line"></span><span class="nav-menu-line"></span><span class="nav-menu-line"></span></span></button>
1396
+ <div class="nav-actions">
1397
+ <button class="theme-toggle" id="themeToggle" type="button" aria-label="Switch theme" title="Switch theme"><span class="theme-toggle-icon" aria-hidden="true"></span></button>
1398
+ <button class="nav-menu" id="navMenu" type="button" aria-expanded="false" aria-controls="navLinks" aria-label="Open navigation"><span class="nav-menu-lines" aria-hidden="true"><span class="nav-menu-line"></span><span class="nav-menu-line"></span><span class="nav-menu-line"></span></span></button>
1399
+ </div>
1231
1400
  <div class="nav-links" id="navLinks">
1232
1401
  <a href="/dashboard">Dashboard</a>
1233
- <button class="theme-toggle" id="themeToggle" type="button" aria-label="Switch theme" title="Switch theme"><span class="theme-toggle-icon" aria-hidden="true"></span></button>
1234
1402
  <a class="button primary" href="/">Home</a>
1235
1403
  </div>
1236
1404
  </nav>
@@ -1372,6 +1540,7 @@ export function renderDashboardUi(env) {
1372
1540
  <meta charset="utf-8">
1373
1541
  <meta name="viewport" content="width=device-width, initial-scale=1">
1374
1542
  <title>${appName} Dashboard</title>
1543
+ <link rel="icon" href="/favicon.svg" type="image/svg+xml">
1375
1544
  ${themeBootScript()}
1376
1545
  ${sharedCss()}
1377
1546
  <style>
@@ -1720,10 +1889,12 @@ ${sharedCss()}
1720
1889
  <div class="shell">
1721
1890
  <nav class="nav">
1722
1891
  <a href="/" class="brand">ErrorAtlas</a>
1723
- <button class="nav-menu" id="navMenu" type="button" aria-expanded="false" aria-controls="navLinks" aria-label="Open navigation"><span class="nav-menu-lines" aria-hidden="true"><span class="nav-menu-line"></span><span class="nav-menu-line"></span><span class="nav-menu-line"></span></span></button>
1892
+ <div class="nav-actions">
1893
+ <button class="theme-toggle" id="themeToggle" type="button" aria-label="Switch theme" title="Switch theme"><span class="theme-toggle-icon" aria-hidden="true"></span></button>
1894
+ <button class="nav-menu" id="navMenu" type="button" aria-expanded="false" aria-controls="navLinks" aria-label="Open navigation"><span class="nav-menu-lines" aria-hidden="true"><span class="nav-menu-line"></span><span class="nav-menu-line"></span><span class="nav-menu-line"></span></span></button>
1895
+ </div>
1724
1896
  <div class="nav-links" id="navLinks">
1725
1897
  <a href="/setup">Setup</a>
1726
- <button class="theme-toggle" id="themeToggle" type="button" aria-label="Switch theme" title="Switch theme"><span class="theme-toggle-icon" aria-hidden="true"></span></button>
1727
1898
  <a class="button primary" href="/">Home</a>
1728
1899
  </div>
1729
1900
  </nav>
@@ -1985,7 +2156,7 @@ ${sharedCss()}
1985
2156
  function renderAuth() {
1986
2157
  title.textContent = "Sign in";
1987
2158
  status.textContent = "Protected console";
1988
- content.innerHTML = '<div class="panel auth-open-card"><span class="muted">Sign in to mint MCP keys</span><h3>Console access</h3><p>Use GitHub or a one-time email link. New operators are created automatically.</p></div><div class="auth-modal" id="authModal" hidden><div class="auth-dialog" role="dialog" aria-modal="true" aria-labelledby="authTitle"><div class="auth-dialog-head"><div><h3 id="authTitle">Enter ErrorAtlas</h3><p>Pick an auth method.</p></div><button class="icon-button" id="closeAuthModal" type="button" aria-label="Close sign in dialog">×</button></div><div class="auth-methods"><button class="button primary" id="githubSignIn" type="button">Continue with GitHub</button><div class="auth-divider">or</div><input id="authEmail" type="email" placeholder="you@example.com" aria-label="Email address"><button class="button" id="sendEmail" type="button">Send magic link</button></div></div></div>';
2159
+ content.innerHTML = '<div class="panel auth-open-card"><span class="muted">Sign in to mint MCP keys</span><h3>Console access</h3><p>Use GitHub or a one-time email link. Your workspace is prepared automatically.</p></div><div class="auth-modal" id="authModal" hidden><div class="auth-dialog" role="dialog" aria-modal="true" aria-labelledby="authTitle"><div class="auth-dialog-head"><div><h3 id="authTitle">Enter ErrorAtlas</h3><p>Choose how you want to sign in.</p></div><button class="icon-button" id="closeAuthModal" type="button" aria-label="Close sign in dialog">x</button></div><div class="auth-methods"><button class="button primary" id="githubSignIn" type="button">Continue with GitHub</button><div class="auth-divider">or</div><input id="authEmail" type="email" placeholder="you@example.com" aria-label="Email address"><button class="button" id="sendEmail" type="button">Send magic link</button></div></div></div>';
1989
2160
  }
1990
2161
 
1991
2162
  function renderSetupMissing() {
@@ -1996,7 +2167,7 @@ ${sharedCss()}
1996
2167
  }
1997
2168
 
1998
2169
  async function signInWithGitHub() {
1999
- if (!state.config || !state.config.supabaseUrl) return setMessage("Supabase URL is missing.");
2170
+ if (!state.config || !state.config.supabaseUrl) return setMessage("Sign-in is not configured yet.");
2000
2171
  const redirectTo = location.origin + "/dashboard";
2001
2172
  location.assign(supabaseBase() + "/auth/v1/authorize?provider=github&redirect_to=" + encodeURIComponent(redirectTo));
2002
2173
  }
@@ -2010,6 +2181,7 @@ ${sharedCss()}
2010
2181
  body: JSON.stringify({ email: email, create_user: true })
2011
2182
  });
2012
2183
  if (!response.ok) throw new Error(await response.text());
2184
+ closeAuthModal();
2013
2185
  setMessage("Magic link sent. Check your inbox for " + email + ".", "success");
2014
2186
  }
2015
2187
 
@@ -2186,10 +2358,8 @@ ${sharedCss()}
2186
2358
  renderTopBar();
2187
2359
  render();
2188
2360
  } catch (error) {
2189
- saveSession(null);
2190
- state.session = null;
2191
- state.data = null;
2192
- setMessage(error instanceof Error ? error.message : "Dashboard failed to load.");
2361
+ state.data = { agentKeys: [], setupWarning: "Your workspace is still being prepared. Refresh once; if it continues, sign out and sign in again." };
2362
+ setMessage(error instanceof Error ? error.message : "Workspace could not finish loading.");
2193
2363
  renderTopBar();
2194
2364
  render();
2195
2365
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sansynx/erroratlas",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Hosted ErrorAtlas MCP client and local debugging memory tools for coding agents.",
@@ -27,7 +27,6 @@
27
27
  "deploy": "wrangler deploy",
28
28
  "build": "npm run sync:assets && tsc -p tsconfig.json",
29
29
  "typecheck": "tsc --noEmit",
30
- "test:e2e": "npm run build && node scripts/e2e-smoke.mjs",
31
30
  "prepack": "npm run build",
32
31
  "mcp": "tsx src/mcp/server.ts",
33
32
  "cli": "tsx src/cli/index.ts"