@lightupai/polaris 0.0.40 → 0.0.42

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 CHANGED
@@ -15,3 +15,6 @@ SLACK_APP_TOKEN=xapp-...
15
15
 
16
16
  # Long message mode: snippet, thread, or inline
17
17
  POLARIS_LONG_MSG=snippet
18
+
19
+ # Signup notifications — posts to #alerts-mql-stream via Slack bot token (xoxb-...)
20
+ SIGNUP_SLACK_BOT_TOKEN=
@@ -45,6 +45,7 @@ services:
45
45
  SLACK_CLIENT_ID: ${SLACK_CLIENT_ID}
46
46
  SLACK_CLIENT_SECRET: ${SLACK_CLIENT_SECRET}
47
47
  SLACK_REDIRECT_URI: https://app.withpolaris.ai/slack/callback
48
+ SIGNUP_SLACK_BOT_TOKEN: ${SIGNUP_SLACK_BOT_TOKEN:-}
48
49
  depends_on:
49
50
  postgres:
50
51
  condition: service_healthy
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightupai/polaris",
3
- "version": "0.0.40",
3
+ "version": "0.0.42",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "polaris": "bin/polaris",
package/src/web/app.ts CHANGED
@@ -30,6 +30,28 @@ import {
30
30
  mockDevices,
31
31
  } from "./fixtures";
32
32
 
33
+ // --- Signup notifications ---
34
+
35
+ const SIGNUP_CHANNEL = "#alerts-mql-stream";
36
+
37
+ function notifySignup(opts: { name: string; email: string; domain: string; orgName: string; isNewOrg: boolean }): void {
38
+ const botToken = process.env.SIGNUP_SLACK_BOT_TOKEN;
39
+ if (!botToken) return;
40
+
41
+ const emoji = opts.isNewOrg ? ":tada:" : ":wave:";
42
+ const action = opts.isNewOrg ? "signed up (new org)" : "joined";
43
+ const text = `${emoji} *${opts.name}* (${opts.email}) ${action} — ${opts.orgName} (${opts.domain})`;
44
+
45
+ fetch("https://slack.com/api/chat.postMessage", {
46
+ method: "POST",
47
+ headers: {
48
+ Authorization: `Bearer ${botToken}`,
49
+ "Content-Type": "application/json",
50
+ },
51
+ body: JSON.stringify({ channel: SIGNUP_CHANNEL, text }),
52
+ }).catch(() => {});
53
+ }
54
+
33
55
  // --- Google OAuth ---
34
56
 
35
57
  function getGoogle(): Google {
@@ -129,6 +151,20 @@ export function createApp(sql: Sql) {
129
151
  const userId = crypto.randomUUID();
130
152
  const participantId = `user:${name.toLowerCase().replace(/\s+/g, ".")}`;
131
153
  await createUser(sql, userId, email, name, existingOrg.id, participantId);
154
+
155
+ // Notify org's Slack system channel
156
+ postSystemEvent({
157
+ sql,
158
+ orgId: existingOrg.id,
159
+ sender: participantId,
160
+ text: `:wave: *${name}* (${email}) joined the team`,
161
+ botToken: existingOrg.slack_bot_token ?? undefined,
162
+ channelId: existingOrg.slack_system_channel_id ?? undefined,
163
+ }).catch(() => {});
164
+
165
+ // Notify internal team
166
+ notifySignup({ name, email, domain, orgName: existingOrg.name, isNewOrg: false });
167
+
132
168
  const token = await createToken({ sub: userId, email, name, org_id: existingOrg.id, participant_id: participantId });
133
169
  return authRedirect(c, state, token);
134
170
  }
@@ -144,6 +180,10 @@ export function createApp(sql: Sql) {
144
180
  const userId = crypto.randomUUID();
145
181
  const participantId = `user:${name.toLowerCase().replace(/\s+/g, ".")}`;
146
182
  await createUser(sql, userId, email, name, orgId, participantId);
183
+
184
+ // Notify internal team
185
+ notifySignup({ name, email, domain, orgName, isNewOrg: true });
186
+
147
187
  const token = await createToken({ sub: userId, email, name, org_id: orgId, participant_id: participantId });
148
188
  return authRedirect(c, state, token);
149
189
  });
package/src/web/layout.ts CHANGED
@@ -27,7 +27,7 @@ tailwind.config = {
27
27
  }
28
28
  </script>
29
29
  </head>
30
- <body class="bg-gray-50 text-gray-900 antialiased">${body}
30
+ <body class="bg-gray-50 text-gray-900 antialiased"><div class="overflow-x-hidden max-w-[100vw]">${body}</div>
31
31
  <script>
32
32
  document.addEventListener('click', function(e) {
33
33
  const btn = e.target.closest('.polaris-copy');
@@ -69,8 +69,8 @@ export function nav(token?: string, opts?: NavOpts): string {
69
69
  <a href="/" class="block px-3 py-2 text-sm text-gray-700 hover:bg-gray-50">Log out</a>
70
70
  </div>
71
71
  </div>`
72
- : `<a href="/login" class="text-sm font-medium text-gray-500 hover:text-gray-700">Sign in</a>
73
- <a href="/signup" class="inline-flex items-center gap-2 px-4 py-2 bg-white border border-gray-300 rounded-lg shadow-sm hover:bg-gray-50 transition text-sm font-medium text-gray-700">
72
+ : `<a href="/login" class="text-sm font-medium text-gray-500 hover:text-gray-700 hidden sm:block">Sign in</a>
73
+ <a href="/signup" class="inline-flex items-center gap-2 px-3 py-1.5 sm:px-4 sm:py-2 bg-white border border-gray-300 rounded-lg shadow-sm hover:bg-gray-50 transition text-xs sm:text-sm font-medium text-gray-700">
74
74
  <svg width="14" height="14" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"><path d="M17.64 9.2c0-.637-.057-1.251-.164-1.84H9v3.481h4.844a4.14 4.14 0 01-1.796 2.716v2.259h2.908c1.702-1.567 2.684-3.875 2.684-6.615z" fill="#4285F4"/><path d="M9 18c2.43 0 4.467-.806 5.956-2.18l-2.908-2.259c-.806.54-1.837.86-3.048.86-2.344 0-4.328-1.584-5.036-3.711H.957v2.332A8.997 8.997 0 009 18z" fill="#34A853"/><path d="M3.964 10.71A5.41 5.41 0 013.682 9c0-.593.102-1.17.282-1.71V4.958H.957A8.996 8.996 0 000 9c0 1.452.348 2.827.957 4.042l3.007-2.332z" fill="#FBBC05"/><path d="M9 3.58c1.321 0 2.508.454 3.44 1.345l2.582-2.58C13.463.891 11.426 0 9 0A8.997 8.997 0 00.957 4.958L3.964 6.29C4.672 4.163 6.656 2.58 9 3.58z" fill="#EA4335"/></svg>
75
75
  Sign up
76
76
  </a>`;
package/src/web/pages.ts CHANGED
@@ -7,15 +7,15 @@ export function renderLandingPage(): string {
7
7
  return `
8
8
  ${nav()}
9
9
  <!-- Hero: text left, hub diagram right -->
10
- <div class="max-w-6xl mx-auto px-6 pt-24 pb-16">
10
+ <div class="max-w-6xl mx-auto px-4 sm:px-6 pt-16 sm:pt-24 pb-16">
11
11
  <div class="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
12
12
 
13
13
  <!-- Left: text -->
14
- <div>
15
- <h1 class="text-4xl font-bold tracking-tight text-gray-900 sm:text-5xl">
14
+ <div class="min-w-0">
15
+ <h1 class="text-3xl sm:text-4xl font-bold tracking-tight text-gray-900 lg:text-5xl">
16
16
  Meet Polaris.<br>It's like Gong for Claude Code sessions.
17
17
  </h1>
18
- <p class="mt-6 text-lg text-gray-500">
18
+ <p class="mt-6 text-base sm:text-lg text-gray-500">
19
19
  Bring your local Claude Code sessions straight into a collaborative Slack channel. Polaris works in the background to automatically capture and document the AI's entire execution path including all prompts, responses, and tool calls in real time. Teammates can watch the live log stream, intervene with inline commands via Slack, or audit the complete thought process later.
20
20
  </p>
21
21
  </div>
@@ -49,7 +49,8 @@ export function renderLandingPage(): string {
49
49
  </div>
50
50
 
51
51
  <!-- Branching arrows: Polaris ↔ sessions -->
52
- <svg class="w-80 h-16 text-gray-300" viewBox="0 0 320 64" fill="none" stroke="currentColor" stroke-width="1.5" overflow="visible">
52
+ <!-- Branching arrows: desktop -->
53
+ <svg class="hidden md:block w-80 h-16 text-gray-300" viewBox="0 0 320 64" fill="none" stroke="currentColor" stroke-width="1.5" overflow="visible">
53
54
  <path d="M160 0 L160 20" stroke-linecap="round"/>
54
55
  <path d="M157 20 L160 0 L163 20" stroke-linecap="round" stroke-linejoin="round"/>
55
56
  <path d="M160 20 L48 58" stroke-linecap="round"/>
@@ -59,14 +60,19 @@ export function renderLandingPage(): string {
59
60
  <path d="M160 20 L272 58" stroke-linecap="round"/>
60
61
  <path d="M260 54 L272 58 L268 46" stroke-linecap="round" stroke-linejoin="round"/>
61
62
  </svg>
63
+ <!-- Branching arrow: mobile (simple vertical) -->
64
+ <svg class="md:hidden w-5 h-8 text-gray-300" viewBox="0 0 20 32" fill="none" stroke="currentColor" stroke-width="1.5">
65
+ <path d="M7 4 L7 28 M4 7 L7 4 L10 7" stroke-linecap="round" stroke-linejoin="round"/>
66
+ <path d="M13 28 L13 4 M10 25 L13 28 L16 25" stroke-linecap="round" stroke-linejoin="round"/>
67
+ </svg>
62
68
 
63
69
  <!-- Session nodes -->
64
- <div class="flex items-start gap-6">
70
+ <div class="flex flex-wrap justify-center items-start gap-4 md:gap-6">
65
71
  <div class="flex items-center gap-2 px-4 py-2.5 bg-gray-900 rounded-lg shadow-sm">
66
72
  <img class="w-5 h-5" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAoAAAAKACAMAAAA7EzkRAAAAFVBMVEVMaXHZd1fZd1babUjZd1faf1rZd1epRaWRAAAABnRSTlMAXawH8g5t5RLrAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFOklEQVR42u3WUQ6EIAxAQcDV+x95r1Bjk2Kdid81wkMdAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADebtHa9gFedPYTIAIUoAAFiAAFKEABIkABClCACFCAAhQgAkSAAkSACFCACBABChABIkABIkAEKEAEiAAFiAARoAARIAIUIAJEgAJEgAhQgAgQAQoQASJAAQpQgAhQgAIUIAIUoAAFiAAFKEABIkABClCACBABChABIkABIkAEKEAEiAAFiAARoAARIAIUIAJEgAJEgAhQgAgQAQoQASJAASJABChABIgABShAASJAAQpQgAhQgAIUIAIUoAAFiAARoAARIAIUIAJEgAJEgAhQgAjwnjNmfq2EGVwYAT4UvO33AqzZDwEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoACTHTHfCzC4MNkBHsnGBYUEiAARIAgQAYIAESAIEAGCABEgCBABggARIAgQAYIAESAIEAGCABEgCBABggARIAgQAYIAESAIEAEiQBAgAgQBIkAQIAIEASJAECACBAEiQBAgAgQBIkAQIAIEASJAECACBAEiQBAgAgQBIkAQIAIEASJABAgCRIAgQAQIAkSAIEAECAJEgCBABAgCRIDwNMCVzJL2lt3LyGaLmr+xdmeLBChABIgABYgAEaAAESACFCACRIACRIAIUIAIEAEKEAEiQAEiQAQoQASIAAWIABGgABEgAhQgAkSAAhSgABGgAAUoQAQoQAEKEAEKUIACRIAIUIAIEAEKEAEiQAEiQAQoQASIAAWIABGgABEgAhQgAkSAAkSACFCACBABChABIkABIkAEKEABChABClCAAkSAAhSgABGgAAUoQAQoQAEKEAEiQAEiQAQoQASIAAWIABGgABEgAhQgAkSAAkSACDDJjFnRBy6aVyb6HKto3mhiJp+4W/OOXa8bX5CZ/EVqU9YbAuzwCyNAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKUIACFKAABShAAQpQgAIUoAAFKEABClCAAhSgAAUoQAEKcEvnDCqaV3cyg86ieQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJv8Af3P8SOrUE9bAAAAAElFTkSuQmCC"/>
67
73
  <span class="text-xs text-gray-300">Alice working on auth</span>
68
74
  </div>
69
- <div class="flex items-center gap-2 px-4 py-2.5 mt-10 bg-white border border-gray-200 rounded-lg shadow-sm">
75
+ <div class="flex items-center gap-2 px-4 py-2.5 md:mt-10 bg-white border border-gray-200 rounded-lg shadow-sm">
70
76
  <svg class="w-4 h-4 text-blue-500" viewBox="0 0 16 16" fill="currentColor"><path d="M8 0C8 4.4 4.4 8 0 8c4.4 0 8 3.6 8 8 0-4.4 3.6-8 8-8-4.4 0-8-3.6-8-8z"/></svg>
71
77
  <span class="text-xs text-gray-700">Martha writing docs</span>
72
78
  </div>
@@ -150,8 +156,14 @@ export function renderLandingPage(): string {
150
156
  </div>
151
157
  </div>
152
158
 
153
- <!-- Arrows -->
154
- <div class="max-w-[30rem] mx-auto grid grid-cols-2 gap-4 py-3">
159
+ <!-- Arrows: single on mobile, two-column on desktop -->
160
+ <div class="flex justify-center md:hidden py-3">
161
+ <svg class="w-6 h-10 text-gray-300" viewBox="0 0 24 40" fill="none" stroke="currentColor" stroke-width="2">
162
+ <path d="M8 4 L8 36 M4 8 L8 4 L12 8" stroke-linecap="round" stroke-linejoin="round"/>
163
+ <path d="M16 36 L16 4 M12 32 L16 36 L20 32" stroke-linecap="round" stroke-linejoin="round"/>
164
+ </svg>
165
+ </div>
166
+ <div class="max-w-[30rem] mx-auto hidden md:grid grid-cols-2 gap-4 py-3">
155
167
  <div class="flex justify-center">
156
168
  <svg class="w-6 h-10 text-gray-300" viewBox="0 0 24 40" fill="none" stroke="currentColor" stroke-width="2">
157
169
  <path d="M8 4 L8 36 M4 8 L8 4 L12 8" stroke-linecap="round" stroke-linejoin="round"/>
@@ -167,7 +179,7 @@ export function renderLandingPage(): string {
167
179
  </div>
168
180
 
169
181
  <!-- Row 2: CLI sessions -->
170
- <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
182
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4 min-w-0">
171
183
 
172
184
  <!-- Alice's terminal -->
173
185
  <div class="bg-gray-900 rounded-xl overflow-hidden shadow-xl">
@@ -231,17 +243,17 @@ export function renderLandingPage(): string {
231
243
  <div class="mt-8 space-y-8">
232
244
  <div>
233
245
  <h3 class="font-semibold text-gray-900">Start streaming your session</h3>
234
- <div class="mt-2 bg-gray-900 text-gray-300 text-sm px-4 py-2.5 rounded-lg font-mono"><span class="text-polaris-400">&gt;</span> /polaris join #webapp</div>
246
+ <div class="mt-2 bg-gray-900 text-gray-300 text-sm px-4 py-2.5 rounded-lg font-mono overflow-x-auto"><span class="text-polaris-400">&gt;</span> /polaris join #webapp</div>
235
247
  <p class="mt-2 text-sm text-gray-500">Every prompt, response, and tool call streams to your team's Slack channel in real time.</p>
236
248
  </div>
237
249
  <div>
238
250
  <h3 class="font-semibold text-gray-900">Pull a teammate into your session</h3>
239
- <div class="mt-2 bg-gray-900 text-gray-300 text-sm px-4 py-2.5 rounded-lg font-mono"><span class="text-polaris-400">&gt;</span> /polaris tag @bob I need your input on this auth approach</div>
251
+ <div class="mt-2 bg-gray-900 text-gray-300 text-sm px-4 py-2.5 rounded-lg font-mono overflow-x-auto"><span class="text-polaris-400">&gt;</span> /polaris tag @bob I need your input on this auth approach</div>
240
252
  <p class="mt-2 text-sm text-gray-500">Bob gets notified in Slack with full context. His reply appears inline in your terminal.</p>
241
253
  </div>
242
254
  <div>
243
255
  <h3 class="font-semibold text-gray-900">Catch up on a teammate's session</h3>
244
- <div class="mt-2 bg-white border border-gray-200 rounded-lg px-4 py-2.5 flex items-center gap-2">
256
+ <div class="mt-2 bg-white border border-gray-200 rounded-lg px-4 py-2.5 flex items-start gap-2 overflow-x-auto">
245
257
  <svg class="w-4 h-4 text-[#4A154B] shrink-0" viewBox="0 0 24 24" fill="currentColor"><path d="M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zm1.271 0a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313zM8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834zm0 1.271a2.528 2.528 0 0 1 2.521 2.521 2.528 2.528 0 0 1-2.521 2.521H2.522A2.528 2.528 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312zm10.124 2.521a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.52 2.521h-2.522V8.834zm-1.271 0a2.528 2.528 0 0 1-2.521 2.521 2.528 2.528 0 0 1-2.521-2.521V2.522A2.528 2.528 0 0 1 15.165 0a2.528 2.528 0 0 1 2.522 2.522v6.312zm-2.522 10.124a2.528 2.528 0 0 1 2.522 2.522A2.528 2.528 0 0 1 15.165 24a2.527 2.527 0 0 1-2.521-2.52v-2.523h2.521zm0-1.271a2.527 2.527 0 0 1-2.521-2.521 2.528 2.528 0 0 1 2.521-2.521h6.313A2.528 2.528 0 0 1 24 15.165a2.528 2.528 0 0 1-2.522 2.522h-6.313z"/></svg>
246
258
  <span class="text-sm text-gray-700 font-mono"><span class="text-blue-600 font-medium">@polaris</span> summarize alice last 2h</span>
247
259
  </div>
@@ -249,7 +261,7 @@ export function renderLandingPage(): string {
249
261
  </div>
250
262
  <div>
251
263
  <h3 class="font-semibold text-gray-900">Redirect an agent from Slack</h3>
252
- <div class="mt-2 bg-white border border-gray-200 rounded-lg px-4 py-2.5 flex items-center gap-2">
264
+ <div class="mt-2 bg-white border border-gray-200 rounded-lg px-4 py-2.5 flex items-start gap-2 overflow-x-auto">
253
265
  <svg class="w-4 h-4 text-[#4A154B] shrink-0" viewBox="0 0 24 24" fill="currentColor"><path d="M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zm1.271 0a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313zM8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834zm0 1.271a2.528 2.528 0 0 1 2.521 2.521 2.528 2.528 0 0 1-2.521 2.521H2.522A2.528 2.528 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312zm10.124 2.521a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.52 2.521h-2.522V8.834zm-1.271 0a2.528 2.528 0 0 1-2.521 2.521 2.528 2.528 0 0 1-2.521-2.521V2.522A2.528 2.528 0 0 1 15.165 0a2.528 2.528 0 0 1 2.522 2.522v6.312zm-2.522 10.124a2.528 2.528 0 0 1 2.522 2.522A2.528 2.528 0 0 1 15.165 24a2.527 2.527 0 0 1-2.521-2.52v-2.523h2.521zm0-1.271a2.527 2.527 0 0 1-2.521-2.521 2.528 2.528 0 0 1 2.521-2.521h6.313A2.528 2.528 0 0 1 24 15.165a2.528 2.528 0 0 1-2.522 2.522h-6.313z"/></svg>
254
266
  <span class="text-sm text-gray-700 font-mono"><span class="text-blue-600 font-medium">@agent.alice</span> use RS256, not HS256 &mdash; we need asymmetric keys</span>
255
267
  </div>
@@ -257,12 +269,12 @@ export function renderLandingPage(): string {
257
269
  </div>
258
270
  <div>
259
271
  <h3 class="font-semibold text-gray-900">Attach session context to a PR</h3>
260
- <div class="mt-2 bg-gray-900 text-gray-300 text-sm px-4 py-2.5 rounded-lg font-mono"><span class="text-polaris-400">&gt;</span> /polaris attach-pr #482</div>
272
+ <div class="mt-2 bg-gray-900 text-gray-300 text-sm px-4 py-2.5 rounded-lg font-mono overflow-x-auto"><span class="text-polaris-400">&gt;</span> /polaris attach-pr #482</div>
261
273
  <p class="mt-2 text-sm text-gray-500">Adds a session transcript — prompts, decisions, and reasoning — to the pull request. Reviewers see the "why," not just the "what."</p>
262
274
  </div>
263
275
  <div>
264
276
  <h3 class="font-semibold text-gray-900">Search past sessions</h3>
265
- <div class="mt-2 bg-white border border-gray-200 rounded-lg px-4 py-2.5 flex items-center gap-2">
277
+ <div class="mt-2 bg-white border border-gray-200 rounded-lg px-4 py-2.5 flex items-start gap-2 overflow-x-auto">
266
278
  <svg class="w-4 h-4 text-[#4A154B] shrink-0" viewBox="0 0 24 24" fill="currentColor"><path d="M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zm1.271 0a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313zM8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834zm0 1.271a2.528 2.528 0 0 1 2.521 2.521 2.528 2.528 0 0 1-2.521 2.521H2.522A2.528 2.528 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312zm10.124 2.521a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.52 2.521h-2.522V8.834zm-1.271 0a2.528 2.528 0 0 1-2.521 2.521 2.528 2.528 0 0 1-2.521-2.521V2.522A2.528 2.528 0 0 1 15.165 0a2.528 2.528 0 0 1 2.522 2.522v6.312zm-2.522 10.124a2.528 2.528 0 0 1 2.522 2.522A2.528 2.528 0 0 1 15.165 24a2.527 2.527 0 0 1-2.521-2.52v-2.523h2.521zm0-1.271a2.527 2.527 0 0 1-2.521-2.521 2.528 2.528 0 0 1 2.521-2.521h6.313A2.528 2.528 0 0 1 24 15.165a2.528 2.528 0 0 1-2.522 2.522h-6.313z"/></svg>
267
279
  <span class="text-sm text-gray-700 font-mono"><span class="text-blue-600 font-medium">@polaris</span> search "webhook secret rotation"</span>
268
280
  </div>
@@ -330,6 +342,31 @@ export function renderLandingPage(): string {
330
342
  </div>
331
343
  </div>
332
344
 
345
+ <!-- The Vision -->
346
+ <div class="py-16 border-t border-gray-200">
347
+ <div class="max-w-3xl mx-auto px-6">
348
+ <h2 class="text-sm font-semibold text-gray-400 uppercase tracking-wider text-center">The vision</h2>
349
+ <p class="mt-6 text-gray-700 text-center max-w-2xl mx-auto">Tobi Lutke, CEO of Shopify, recently described a future where every AI interaction in an organization flows through a shared, observable stream — what he calls "the shop floor." The vision he articulates is strikingly close to what Polaris already does.</p>
350
+
351
+ <div class="mt-8 flex justify-center">
352
+ <a href="https://x.com/tobi/article/2053121182044451016" target="_blank" class="block max-w-md w-full bg-white border border-gray-200 rounded-xl shadow-sm hover:shadow-md transition overflow-hidden">
353
+ <div class="px-6 py-5">
354
+ <div class="flex items-center gap-2 text-xs text-gray-400">
355
+ <svg class="w-4 h-4" viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>
356
+ <span>Article</span>
357
+ </div>
358
+ <h3 class="mt-3 text-lg font-bold text-gray-900 leading-snug">Learning on the Shop Floor</h3>
359
+ <p class="mt-1 text-sm text-gray-500">by Tobi Lutke &middot; May 9, 2026</p>
360
+ <p class="mt-3 text-xs text-polaris-600 font-medium">Read on X &rarr;</p>
361
+ </div>
362
+ </a>
363
+ </div>
364
+
365
+ <p class="mt-8 text-gray-700 text-center max-w-2xl mx-auto">The idea behind Polaris was born independently, but the convergence isn't a coincidence. As AI agents become central to how software gets built, the need for this layer — variously called a <em>context graph</em>, a <em>memory layer</em>, <em>institutional memory</em>, or <em>decision traces</em> — becomes inevitable. A persistent, searchable record of every prompt, every decision, every session. Polaris is building that layer.</p>
366
+
367
+ </div>
368
+ </div>
369
+
333
370
  <div class="max-w-3xl mx-auto px-6">
334
371
 
335
372
  <!-- How it works -->