@agent-native/core 0.168.0 → 0.168.2
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/corpus/README.md +1 -1
- package/corpus/templates/analytics/netlify.toml +4 -4
- package/corpus/templates/assets/netlify.toml +4 -1
- package/corpus/templates/brain/netlify.toml +4 -1
- package/corpus/templates/calendar/netlify.toml +4 -1
- package/corpus/templates/chat/netlify.toml +4 -1
- package/corpus/templates/clips/desktop/src-tauri/src/util.rs +32 -0
- package/corpus/templates/clips/netlify.toml +4 -1
- package/corpus/templates/content/netlify.toml +4 -1
- package/corpus/templates/crm/netlify.toml +6 -1
- package/corpus/templates/design/netlify.toml +4 -1
- package/corpus/templates/dispatch/netlify.toml +4 -1
- package/corpus/templates/factory/netlify.toml +4 -1
- package/corpus/templates/forms/netlify.toml +4 -1
- package/corpus/templates/macros/netlify.toml +3 -3
- package/corpus/templates/mail/actions/view-screen.ts +80 -8
- package/corpus/templates/mail/app/components/email/EmailList.tsx +3 -1
- package/corpus/templates/mail/app/components/email/EmailListItem.tsx +1 -35
- package/corpus/templates/mail/app/components/email/EmailThread.tsx +16 -8
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +146 -87
- package/corpus/templates/mail/app/global.css +21 -0
- package/corpus/templates/mail/app/hooks/use-emails.ts +6 -0
- package/corpus/templates/mail/app/hooks/use-navigation-state.ts +2 -0
- package/corpus/templates/mail/app/lib/inbox-tabs.ts +5 -0
- package/corpus/templates/mail/app/lib/label-colors.ts +47 -0
- package/corpus/templates/mail/app/pages/InboxPage.tsx +25 -8
- package/corpus/templates/mail/netlify.toml +4 -1
- package/corpus/templates/mail/server/handlers/emails.ts +27 -2
- package/corpus/templates/plan/netlify.toml +4 -1
- package/corpus/templates/slides/netlify.toml +4 -1
- package/corpus/templates/tasks/netlify.toml +6 -1
- package/dist/app-config/migration.d.ts +5 -0
- package/dist/app-config/migration.js +11 -0
- package/dist/app-config/schema.d.ts +4 -0
- package/dist/app-config/schema.js +2 -0
- package/dist/client/AgentPanel.d.ts +3 -1
- package/dist/client/AgentPanel.js +2 -2
- package/dist/client/agent-page/AgentTabsPage.d.ts +4 -1
- package/dist/client/agent-page/AgentTabsPage.js +2 -2
- package/dist/client/integrations/IntegrationConnectionChoice.d.ts +4 -1
- package/dist/client/integrations/IntegrationConnectionChoice.js +6 -2
- package/dist/client/onboarding/FirstRunOnboarding.js +9 -5
- package/dist/client/resources/McpIntegrationDialog.d.ts +3 -1
- package/dist/client/resources/McpIntegrationDialog.js +181 -112
- package/dist/client/resources/mcp-integration-catalog.d.ts +1 -0
- package/dist/client/resources/mcp-integration-catalog.js +5 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/db/migrations.js +10 -2
- package/dist/deploy/build.js +6 -0
- package/dist/deploy/netlify-static-headers.d.ts +3 -0
- package/dist/deploy/netlify-static-headers.js +60 -0
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/localization/default-messages.d.ts +9 -0
- package/dist/localization/default-messages.js +9 -0
- package/dist/observability/routes.d.ts +6 -6
- package/dist/provider-api/actions/custom-provider-registration.d.ts +16 -16
- package/dist/provider-api/actions/provider-api.d.ts +17 -17
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/templates/chat/netlify.toml +4 -1
- package/dist/vite/client.js +2 -0
- package/package.json +2 -1
- package/src/templates/chat/netlify.toml +4 -1
package/corpus/README.md
CHANGED
|
@@ -4,7 +4,7 @@ ignore = "node ./scripts/netlify-ignore-build.mjs analytics"
|
|
|
4
4
|
# core explicitly here so a cached Netlify install cannot bundle an older agent
|
|
5
5
|
# loop into Analytics' server functions (including A2A/MCP final-response
|
|
6
6
|
# guards).
|
|
7
|
-
command = "export DATABASE_URL=${NETLIFY_DATABASE_URL:-$DATABASE_URL} && pnpm install && pnpm --filter @agent-native/core build && NITRO_PRESET=netlify pnpm --filter analytics build && if [ \"${CONTEXT:-}\" = \"production\" ]; then pnpm --filter analytics migrate:production; fi"
|
|
7
|
+
command = "export DATABASE_URL=${NETLIFY_DATABASE_URL:-$DATABASE_URL} && pnpm install && pnpm --filter @agent-native/core build && NITRO_PRESET=netlify pnpm --filter analytics build && if [ \"${CONTEXT:-}\" = \"production\" ] || [ \"${AGENT_NATIVE_RUN_RELEASE_MIGRATIONS:-}\" = \"1\" ]; then pnpm --filter analytics migrate:production; fi"
|
|
8
8
|
publish = "templates/analytics/dist"
|
|
9
9
|
functions = "templates/analytics/.netlify/functions-internal"
|
|
10
10
|
|
|
@@ -27,9 +27,9 @@ AGENT_NATIVE_DISABLE_KEEP_WARM_BACKGROUND = "1"
|
|
|
27
27
|
# its loading screen while those chunks return 404.
|
|
28
28
|
AGENT_NATIVE_SSR_CACHE = "30s"
|
|
29
29
|
# Analytics owns its schema at release time (`migrate:production` above), so
|
|
30
|
-
# production request functions skip migrations.
|
|
31
|
-
# request-path migrations
|
|
32
|
-
#
|
|
30
|
+
# production request functions skip migrations. Ordinary deploy previews still
|
|
31
|
+
# run request-path migrations; the beta prebuilt lane explicitly supplies the
|
|
32
|
+
# release owner even though it builds with branch-deploy context.
|
|
33
33
|
AGENT_NATIVE_RELEASE_MIGRATIONS = "1"
|
|
34
34
|
|
|
35
35
|
# A2A delegation calls run a full agent loop (LLM + tool calls + DB queries).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[build]
|
|
2
2
|
ignore = "node ./scripts/netlify-ignore-build.mjs assets"
|
|
3
|
-
command = "export DATABASE_URL=${NETLIFY_DATABASE_URL:-$DATABASE_URL} && pnpm install && NITRO_PRESET=netlify pnpm --filter assets build && if [ \"${CONTEXT:-}\" = \"production\" ]; then pnpm --filter assets migrate:production; fi"
|
|
3
|
+
command = "export DATABASE_URL=${NETLIFY_DATABASE_URL:-$DATABASE_URL} && pnpm install && NITRO_PRESET=netlify pnpm --filter assets build && if [ \"${CONTEXT:-}\" = \"production\" ] || [ \"${AGENT_NATIVE_RUN_RELEASE_MIGRATIONS:-}\" = \"1\" ]; then pnpm --filter assets migrate:production; fi"
|
|
4
4
|
publish = "templates/assets/dist"
|
|
5
5
|
functions = "templates/assets/.netlify/functions-internal"
|
|
6
6
|
|
|
@@ -15,6 +15,9 @@ AGENT_CHAT_DURABLE_BACKGROUND = "true"
|
|
|
15
15
|
AGENT_NATIVE_HOSTED_HARNESS = "true"
|
|
16
16
|
AGENT_NATIVE_ENABLE_KEEP_WARM = "1"
|
|
17
17
|
AGENT_NATIVE_DISABLE_KEEP_WARM_BACKGROUND = "1"
|
|
18
|
+
# Production release migrations own schema setup; request functions must skip
|
|
19
|
+
# migration probes on every serverless invocation.
|
|
20
|
+
AGENT_NATIVE_RELEASE_MIGRATIONS = "1"
|
|
18
21
|
|
|
19
22
|
# Emit the Netlify background function used by Assets' durable agent-chat opt-in.
|
|
20
23
|
# The normal server timeout still protects non-agent image/video HTTP handlers.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[build]
|
|
2
2
|
ignore = "node ./scripts/netlify-ignore-build.mjs brain"
|
|
3
|
-
command = "export DATABASE_URL=${NETLIFY_DATABASE_URL:-$DATABASE_URL} && pnpm install && NITRO_PRESET=netlify pnpm --filter brain build && pnpm exec tsx templates/brain/jobs/emit-netlify-brain-queue-cron.ts && if [ \"${CONTEXT:-}\" = \"production\" ]; then pnpm --filter brain migrate:production; fi"
|
|
3
|
+
command = "export DATABASE_URL=${NETLIFY_DATABASE_URL:-$DATABASE_URL} && pnpm install && NITRO_PRESET=netlify pnpm --filter brain build && pnpm exec tsx templates/brain/jobs/emit-netlify-brain-queue-cron.ts && if [ \"${CONTEXT:-}\" = \"production\" ] || [ \"${AGENT_NATIVE_RUN_RELEASE_MIGRATIONS:-}\" = \"1\" ]; then pnpm --filter brain migrate:production; fi"
|
|
4
4
|
publish = "templates/brain/dist"
|
|
5
5
|
functions = "templates/brain/.netlify/functions-internal"
|
|
6
6
|
|
|
@@ -14,6 +14,9 @@ NPM_CONFIG_PRODUCTION = "false"
|
|
|
14
14
|
AGENT_NATIVE_HOSTED_HARNESS = "true"
|
|
15
15
|
AGENT_NATIVE_ENABLE_KEEP_WARM = "1"
|
|
16
16
|
AGENT_NATIVE_DISABLE_KEEP_WARM_BACKGROUND = "1"
|
|
17
|
+
# Production release migrations own schema setup; request functions must skip
|
|
18
|
+
# migration probes on every serverless invocation.
|
|
19
|
+
AGENT_NATIVE_RELEASE_MIGRATIONS = "1"
|
|
17
20
|
|
|
18
21
|
# A2A delegation calls run a full agent loop (LLM + tool calls + DB queries).
|
|
19
22
|
# The framework does not impose a default agent-run timeout; if a template sets
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[build]
|
|
2
2
|
ignore = "node ./scripts/netlify-ignore-build.mjs calendar"
|
|
3
|
-
command = "export DATABASE_URL=${NETLIFY_DATABASE_URL:-$DATABASE_URL} && pnpm install && DATABASE_URL=${NETLIFY_DATABASE_URL_UNPOOLED:-$DATABASE_URL} NITRO_PRESET=netlify pnpm --filter calendar build && if [ \"${CONTEXT:-}\" = \"production\" ]; then DATABASE_URL=${NETLIFY_DATABASE_URL_UNPOOLED:-$DATABASE_URL} pnpm --filter calendar migrate:production; fi"
|
|
3
|
+
command = "export DATABASE_URL=${NETLIFY_DATABASE_URL:-$DATABASE_URL} && pnpm install && DATABASE_URL=${NETLIFY_DATABASE_URL_UNPOOLED:-$DATABASE_URL} NITRO_PRESET=netlify pnpm --filter calendar build && if [ \"${CONTEXT:-}\" = \"production\" ] || [ \"${AGENT_NATIVE_RUN_RELEASE_MIGRATIONS:-}\" = \"1\" ]; then DATABASE_URL=${NETLIFY_DATABASE_URL_UNPOOLED:-$DATABASE_URL} pnpm --filter calendar migrate:production; fi"
|
|
4
4
|
publish = "templates/calendar/dist"
|
|
5
5
|
functions = "templates/calendar/.netlify/functions-internal"
|
|
6
6
|
|
|
@@ -14,6 +14,9 @@ NPM_CONFIG_PRODUCTION = "false"
|
|
|
14
14
|
AGENT_NATIVE_HOSTED_HARNESS = "true"
|
|
15
15
|
AGENT_NATIVE_ENABLE_KEEP_WARM = "1"
|
|
16
16
|
AGENT_NATIVE_DISABLE_KEEP_WARM_BACKGROUND = "1"
|
|
17
|
+
# Production release migrations own schema setup; request functions must skip
|
|
18
|
+
# migration probes on every serverless invocation.
|
|
19
|
+
AGENT_NATIVE_RELEASE_MIGRATIONS = "1"
|
|
17
20
|
|
|
18
21
|
# A2A delegation calls run a full agent loop (LLM + tool calls + DB queries).
|
|
19
22
|
# The framework does not impose a default agent-run timeout; if a template sets
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[build]
|
|
2
2
|
ignore = "node ./scripts/netlify-ignore-build.mjs chat"
|
|
3
|
-
command = "export DATABASE_URL=${NETLIFY_DATABASE_URL:-$DATABASE_URL} && pnpm install && NITRO_PRESET=netlify pnpm --filter chat build && if [ \"${CONTEXT:-}\" = \"production\" ]; then pnpm --filter chat migrate:production; fi"
|
|
3
|
+
command = "export DATABASE_URL=${NETLIFY_DATABASE_URL:-$DATABASE_URL} && pnpm install && NITRO_PRESET=netlify pnpm --filter chat build && if [ \"${CONTEXT:-}\" = \"production\" ] || [ \"${AGENT_NATIVE_RUN_RELEASE_MIGRATIONS:-}\" = \"1\" ]; then pnpm --filter chat migrate:production; fi"
|
|
4
4
|
publish = "templates/chat/dist"
|
|
5
5
|
functions = "templates/chat/.netlify/functions-internal"
|
|
6
6
|
|
|
@@ -14,3 +14,6 @@ NPM_CONFIG_PRODUCTION = "false"
|
|
|
14
14
|
AGENT_NATIVE_HOSTED_HARNESS = "true"
|
|
15
15
|
AGENT_NATIVE_ENABLE_KEEP_WARM = "1"
|
|
16
16
|
AGENT_NATIVE_DISABLE_KEEP_WARM_BACKGROUND = "1"
|
|
17
|
+
# Production release migrations own schema setup; request functions must skip
|
|
18
|
+
# migration probes on every serverless invocation.
|
|
19
|
+
AGENT_NATIVE_RELEASE_MIGRATIONS = "1"
|
|
@@ -2,6 +2,7 @@ use std::{
|
|
|
2
2
|
ffi::OsString,
|
|
3
3
|
path::{Path, PathBuf},
|
|
4
4
|
process::{Command, Stdio},
|
|
5
|
+
sync::atomic::{AtomicU64, Ordering},
|
|
5
6
|
};
|
|
6
7
|
|
|
7
8
|
use tauri::{AppHandle, Emitter, Manager, WebviewUrl, WebviewWindow, WebviewWindowBuilder};
|
|
@@ -13,6 +14,7 @@ use crate::state::{
|
|
|
13
14
|
};
|
|
14
15
|
|
|
15
16
|
const POPOVER_SHADOW_GUTTER_LOGICAL: f64 = 24.0;
|
|
17
|
+
static OAUTH_WINDOW_COUNTER: AtomicU64 = AtomicU64::new(0);
|
|
16
18
|
const POPOVER_DEFAULT_WIDTH_LOGICAL: f64 = 320.0;
|
|
17
19
|
const POPOVER_DEFAULT_HEIGHT_LOGICAL: f64 = 520.0;
|
|
18
20
|
|
|
@@ -109,6 +111,7 @@ pub fn set_capture_included(window: &WebviewWindow) {
|
|
|
109
111
|
|
|
110
112
|
pub fn build_popover_window(app: &mut tauri::App) -> Result<WebviewWindow, tauri::Error> {
|
|
111
113
|
let gutter = POPOVER_SHADOW_GUTTER_LOGICAL * 2.0;
|
|
114
|
+
let app_handle = app.handle().clone();
|
|
112
115
|
WebviewWindowBuilder::new(app, "popover", WebviewUrl::App("index.html".into()))
|
|
113
116
|
.title("Clips")
|
|
114
117
|
.inner_size(
|
|
@@ -126,6 +129,35 @@ pub fn build_popover_window(app: &mut tauri::App) -> Result<WebviewWindow, tauri
|
|
|
126
129
|
.focused(true)
|
|
127
130
|
.shadow(false)
|
|
128
131
|
.accept_first_mouse(true)
|
|
132
|
+
// Tauri does not create a native child for window.open by default.
|
|
133
|
+
// Create it here with the opener's webview configuration so Google
|
|
134
|
+
// OAuth stays in a visible child window and shares the binding cookie.
|
|
135
|
+
.on_new_window(move |url, features| {
|
|
136
|
+
let label = format!(
|
|
137
|
+
"google-oauth-{}",
|
|
138
|
+
OAUTH_WINDOW_COUNTER.fetch_add(1, Ordering::Relaxed)
|
|
139
|
+
);
|
|
140
|
+
let popup = WebviewWindowBuilder::new(&app_handle, label, WebviewUrl::External(url))
|
|
141
|
+
.title("Sign in to Clips")
|
|
142
|
+
.inner_size(520.0, 720.0)
|
|
143
|
+
.resizable(true)
|
|
144
|
+
.always_on_top(false)
|
|
145
|
+
.focused(true)
|
|
146
|
+
.window_features(features)
|
|
147
|
+
.build();
|
|
148
|
+
|
|
149
|
+
match popup {
|
|
150
|
+
Ok(window) => {
|
|
151
|
+
set_capture_excluded(&window);
|
|
152
|
+
configure_overlay_behavior(&window);
|
|
153
|
+
tauri::webview::NewWindowResponse::Create { window }
|
|
154
|
+
}
|
|
155
|
+
Err(error) => {
|
|
156
|
+
eprintln!("[clips-tray] failed to create OAuth popup: {error}");
|
|
157
|
+
tauri::webview::NewWindowResponse::Deny
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
})
|
|
129
161
|
.build()
|
|
130
162
|
}
|
|
131
163
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[build]
|
|
2
2
|
ignore = "node ./scripts/netlify-ignore-build.mjs clips"
|
|
3
|
-
command = "export DATABASE_URL=${NETLIFY_DATABASE_URL:-$DATABASE_URL} && if [ \"${agentNativePrebuiltBuild:-}\" = \"true\" ]; then export DATABASE_URL=\"${agentNativePrebuiltDatabaseUrl:?}\" BETTER_AUTH_SECRET=\"${agentNativePrebuiltAuthSecret:?}\"; fi && pnpm install && NITRO_PRESET=netlify pnpm --filter clips build && pnpm exec tsx templates/clips/jobs/emit-netlify-brain-export-cron.ts && if [ \"${CONTEXT:-}\" = \"production\" ] && [ \"${agentNativePrebuiltBuild:-}\" != \"true\" ]; then pnpm --filter clips migrate:production; fi"
|
|
3
|
+
command = "export DATABASE_URL=${NETLIFY_DATABASE_URL:-$DATABASE_URL} && if [ \"${agentNativePrebuiltBuild:-}\" = \"true\" ]; then export DATABASE_URL=\"${agentNativePrebuiltDatabaseUrl:?}\" BETTER_AUTH_SECRET=\"${agentNativePrebuiltAuthSecret:?}\"; fi && pnpm install && NITRO_PRESET=netlify pnpm --filter clips build && pnpm exec tsx templates/clips/jobs/emit-netlify-brain-export-cron.ts && if { [ \"${CONTEXT:-}\" = \"production\" ] || [ \"${AGENT_NATIVE_RUN_RELEASE_MIGRATIONS:-}\" = \"1\" ]; } && [ \"${agentNativePrebuiltBuild:-}\" != \"true\" ]; then pnpm --filter clips migrate:production; fi"
|
|
4
4
|
publish = "templates/clips/dist"
|
|
5
5
|
functions = "templates/clips/.netlify/functions-internal"
|
|
6
6
|
|
|
@@ -18,6 +18,9 @@ AGENT_CHAT_DURABLE_BACKGROUND = "true"
|
|
|
18
18
|
AGENT_NATIVE_HOSTED_HARNESS = "true"
|
|
19
19
|
AGENT_NATIVE_ENABLE_KEEP_WARM = "1"
|
|
20
20
|
AGENT_NATIVE_DISABLE_KEEP_WARM_BACKGROUND = "1"
|
|
21
|
+
# Production release migrations own schema setup; request functions must skip
|
|
22
|
+
# migration probes on every serverless invocation.
|
|
23
|
+
AGENT_NATIVE_RELEASE_MIGRATIONS = "1"
|
|
21
24
|
|
|
22
25
|
# Netlify Functions run on linux/x64 (AWS Lambda). This tells the deploy build
|
|
23
26
|
# to bundle the matching ffmpeg-static binary into the server bundle so frame
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[build]
|
|
2
2
|
ignore = "node ./scripts/netlify-ignore-build.mjs content"
|
|
3
|
-
command = "export DATABASE_URL=${NETLIFY_DATABASE_URL:-$DATABASE_URL} && pnpm install && NITRO_PRESET=netlify pnpm --filter content build && if [ \"${CONTEXT:-}\" = \"production\" ]; then pnpm --filter content migrate:production; fi"
|
|
3
|
+
command = "export DATABASE_URL=${NETLIFY_DATABASE_URL:-$DATABASE_URL} && pnpm install && NITRO_PRESET=netlify pnpm --filter content build && if [ \"${CONTEXT:-}\" = \"production\" ] || [ \"${AGENT_NATIVE_RUN_RELEASE_MIGRATIONS:-}\" = \"1\" ]; then pnpm --filter content migrate:production; fi"
|
|
4
4
|
publish = "templates/content/dist"
|
|
5
5
|
functions = "templates/content/.netlify/functions-internal"
|
|
6
6
|
|
|
@@ -18,6 +18,9 @@ AGENT_CHAT_DURABLE_BACKGROUND = "true"
|
|
|
18
18
|
AGENT_NATIVE_HOSTED_HARNESS = "true"
|
|
19
19
|
AGENT_NATIVE_ENABLE_KEEP_WARM = "1"
|
|
20
20
|
AGENT_NATIVE_DISABLE_KEEP_WARM_BACKGROUND = "1"
|
|
21
|
+
# Production release migrations own schema setup; request functions must skip
|
|
22
|
+
# migration probes on every serverless invocation.
|
|
23
|
+
AGENT_NATIVE_RELEASE_MIGRATIONS = "1"
|
|
21
24
|
|
|
22
25
|
# A2A delegation calls run a full agent loop (LLM + tool calls + DB queries).
|
|
23
26
|
# The framework does not impose a default agent-run timeout; if a template sets
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[build]
|
|
2
2
|
ignore = "node ./scripts/netlify-ignore-build.mjs crm"
|
|
3
|
-
command = "export DATABASE_URL=${NETLIFY_DATABASE_URL:-$DATABASE_URL} && pnpm install && NITRO_PRESET=netlify pnpm --filter crm build && if [ \"${CONTEXT:-}\" = \"production\" ]; then pnpm --filter crm migrate:production; fi"
|
|
3
|
+
command = "export DATABASE_URL=${NETLIFY_DATABASE_URL:-$DATABASE_URL} && pnpm install && NITRO_PRESET=netlify pnpm --filter crm build && if [ \"${CONTEXT:-}\" = \"production\" ] || [ \"${AGENT_NATIVE_RUN_RELEASE_MIGRATIONS:-}\" = \"1\" ]; then pnpm --filter crm migrate:production; fi"
|
|
4
4
|
publish = "templates/crm/dist"
|
|
5
5
|
functions = "templates/crm/.netlify/functions-internal"
|
|
6
6
|
|
|
@@ -9,3 +9,8 @@ GA_MEASUREMENT_ID = "G-ESF7FYXGN9"
|
|
|
9
9
|
GTM_CONTAINER_ID = "GTM-N3WSTXZ"
|
|
10
10
|
NITRO_PRESET = "netlify"
|
|
11
11
|
NPM_CONFIG_PRODUCTION = "false"
|
|
12
|
+
|
|
13
|
+
[context.production.environment]
|
|
14
|
+
# Production release migrations own schema setup; request functions must skip
|
|
15
|
+
# migration probes on every serverless invocation.
|
|
16
|
+
AGENT_NATIVE_RELEASE_MIGRATIONS = "1"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[build]
|
|
2
2
|
ignore = "node ./scripts/netlify-ignore-build.mjs design"
|
|
3
|
-
command = "export DATABASE_URL=${NETLIFY_DATABASE_URL:-$DATABASE_URL} && pnpm install && NITRO_PRESET=netlify pnpm --filter design build && if [ \"${CONTEXT:-}\" = \"production\" ]; then pnpm --filter design migrate:production; fi"
|
|
3
|
+
command = "export DATABASE_URL=${NETLIFY_DATABASE_URL:-$DATABASE_URL} && pnpm install && NITRO_PRESET=netlify pnpm --filter design build && if [ \"${CONTEXT:-}\" = \"production\" ] || [ \"${AGENT_NATIVE_RUN_RELEASE_MIGRATIONS:-}\" = \"1\" ]; then pnpm --filter design migrate:production; fi"
|
|
4
4
|
publish = "templates/design/dist"
|
|
5
5
|
functions = "templates/design/.netlify/functions-internal"
|
|
6
6
|
|
|
@@ -15,6 +15,9 @@ AGENT_CHAT_DURABLE_BACKGROUND = "true"
|
|
|
15
15
|
AGENT_NATIVE_HOSTED_HARNESS = "true"
|
|
16
16
|
AGENT_NATIVE_ENABLE_KEEP_WARM = "1"
|
|
17
17
|
AGENT_NATIVE_DISABLE_KEEP_WARM_BACKGROUND = "1"
|
|
18
|
+
# Production release migrations own schema setup; request functions must skip
|
|
19
|
+
# migration probes on every serverless invocation.
|
|
20
|
+
AGENT_NATIVE_RELEASE_MIGRATIONS = "1"
|
|
18
21
|
|
|
19
22
|
# Emit the Netlify background function used by Design's durable agent-chat opt-in.
|
|
20
23
|
# The normal server timeout still protects non-background HTTP handlers.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[build]
|
|
2
2
|
ignore = "node ./scripts/netlify-ignore-build.mjs dispatch"
|
|
3
|
-
command = "export DATABASE_URL=${NETLIFY_DATABASE_URL:-$DATABASE_URL} && pnpm install && NITRO_PRESET=netlify pnpm --filter dispatch build && if [ \"${CONTEXT:-}\" = \"production\" ]; then pnpm --filter dispatch migrate:production; fi"
|
|
3
|
+
command = "export DATABASE_URL=${NETLIFY_DATABASE_URL:-$DATABASE_URL} && pnpm install && NITRO_PRESET=netlify pnpm --filter dispatch build && if [ \"${CONTEXT:-}\" = \"production\" ] || [ \"${AGENT_NATIVE_RUN_RELEASE_MIGRATIONS:-}\" = \"1\" ]; then pnpm --filter dispatch migrate:production; fi"
|
|
4
4
|
publish = "templates/dispatch/dist"
|
|
5
5
|
functions = "templates/dispatch/.netlify/functions-internal"
|
|
6
6
|
|
|
@@ -17,6 +17,9 @@ AGENT_CHAT_DURABLE_BACKGROUND = "true"
|
|
|
17
17
|
AGENT_NATIVE_HOSTED_HARNESS = "true"
|
|
18
18
|
AGENT_NATIVE_ENABLE_KEEP_WARM = "1"
|
|
19
19
|
AGENT_NATIVE_DISABLE_KEEP_WARM_BACKGROUND = "1"
|
|
20
|
+
# Production release migrations own schema setup; request functions must skip
|
|
21
|
+
# migration probes on every serverless invocation.
|
|
22
|
+
AGENT_NATIVE_RELEASE_MIGRATIONS = "1"
|
|
20
23
|
|
|
21
24
|
# A2A delegation calls run a full agent loop (LLM + tool calls + DB queries).
|
|
22
25
|
# The framework does not impose a default agent-run timeout; if a template sets
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
[build]
|
|
2
|
-
command = "export DATABASE_URL=\"${NETLIFY_DATABASE_URL:-$DATABASE_URL}\" && pnpm install && DATABASE_URL=\"${NETLIFY_DATABASE_URL_UNPOOLED:-$DATABASE_URL}\" NITRO_PRESET=netlify pnpm --filter factory build && if [ \"${CONTEXT:-}\" = \"production\" ]; then DATABASE_URL=\"${NETLIFY_DATABASE_URL_UNPOOLED:-$DATABASE_URL}\" pnpm --filter factory migrate:production; fi"
|
|
2
|
+
command = "export DATABASE_URL=\"${NETLIFY_DATABASE_URL:-$DATABASE_URL}\" && pnpm install && DATABASE_URL=\"${NETLIFY_DATABASE_URL_UNPOOLED:-$DATABASE_URL}\" NITRO_PRESET=netlify pnpm --filter factory build && if [ \"${CONTEXT:-}\" = \"production\" ] || [ \"${AGENT_NATIVE_RUN_RELEASE_MIGRATIONS:-}\" = \"1\" ]; then DATABASE_URL=\"${NETLIFY_DATABASE_URL_UNPOOLED:-$DATABASE_URL}\" pnpm --filter factory migrate:production; fi"
|
|
3
3
|
publish = "dist"
|
|
4
4
|
functions = ".netlify/functions-internal"
|
|
5
5
|
|
|
@@ -11,3 +11,6 @@ NPM_CONFIG_PRODUCTION = "false"
|
|
|
11
11
|
|
|
12
12
|
[context.production.environment]
|
|
13
13
|
AGENT_NATIVE_HOSTED_HARNESS = "true"
|
|
14
|
+
# Production release migrations own schema setup; request functions must skip
|
|
15
|
+
# migration probes on every serverless invocation.
|
|
16
|
+
AGENT_NATIVE_RELEASE_MIGRATIONS = "1"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[build]
|
|
2
2
|
ignore = "node ./scripts/netlify-ignore-build.mjs forms"
|
|
3
|
-
command = "export DATABASE_URL=${NETLIFY_DATABASE_URL:-$DATABASE_URL} && pnpm install && DATABASE_URL=${NETLIFY_DATABASE_URL_UNPOOLED:-$DATABASE_URL} NITRO_PRESET=netlify pnpm --filter forms build && if [ \"${CONTEXT:-}\" = \"production\" ]; then DATABASE_URL=${NETLIFY_DATABASE_URL_UNPOOLED:-$DATABASE_URL} pnpm --filter forms migrate:production; fi"
|
|
3
|
+
command = "export DATABASE_URL=${NETLIFY_DATABASE_URL:-$DATABASE_URL} && pnpm install && DATABASE_URL=${NETLIFY_DATABASE_URL_UNPOOLED:-$DATABASE_URL} NITRO_PRESET=netlify pnpm --filter forms build && if [ \"${CONTEXT:-}\" = \"production\" ] || [ \"${AGENT_NATIVE_RUN_RELEASE_MIGRATIONS:-}\" = \"1\" ]; then DATABASE_URL=${NETLIFY_DATABASE_URL_UNPOOLED:-$DATABASE_URL} pnpm --filter forms migrate:production; fi"
|
|
4
4
|
publish = "templates/forms/dist"
|
|
5
5
|
functions = "templates/forms/.netlify/functions-internal"
|
|
6
6
|
|
|
@@ -14,6 +14,9 @@ NPM_CONFIG_PRODUCTION = "false"
|
|
|
14
14
|
AGENT_NATIVE_HOSTED_HARNESS = "true"
|
|
15
15
|
AGENT_NATIVE_ENABLE_KEEP_WARM = "1"
|
|
16
16
|
AGENT_NATIVE_DISABLE_KEEP_WARM_BACKGROUND = "1"
|
|
17
|
+
# Production release migrations own schema setup; request functions must skip
|
|
18
|
+
# migration probes on every serverless invocation.
|
|
19
|
+
AGENT_NATIVE_RELEASE_MIGRATIONS = "1"
|
|
17
20
|
|
|
18
21
|
# A2A delegation calls run a full agent loop (LLM + tool calls + DB queries).
|
|
19
22
|
# The framework does not impose a default agent-run timeout; if a template sets
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[build]
|
|
2
2
|
ignore = "node ./scripts/netlify-ignore-build.mjs macros"
|
|
3
|
-
command = "export DATABASE_URL=${NETLIFY_DATABASE_URL:-$DATABASE_URL} && pnpm install && DATABASE_URL=${NETLIFY_DATABASE_URL_UNPOOLED:-$DATABASE_URL} NITRO_PRESET=netlify pnpm --filter macros build && if [ \"${CONTEXT:-}\" = \"production\" ]; then DATABASE_URL=${NETLIFY_DATABASE_URL_UNPOOLED:-$DATABASE_URL} pnpm --filter macros migrate:production; fi"
|
|
3
|
+
command = "export DATABASE_URL=${NETLIFY_DATABASE_URL:-$DATABASE_URL} && pnpm install && DATABASE_URL=${NETLIFY_DATABASE_URL_UNPOOLED:-$DATABASE_URL} NITRO_PRESET=netlify pnpm --filter macros build && if [ \"${CONTEXT:-}\" = \"production\" ] || [ \"${AGENT_NATIVE_RUN_RELEASE_MIGRATIONS:-}\" = \"1\" ]; then DATABASE_URL=${NETLIFY_DATABASE_URL_UNPOOLED:-$DATABASE_URL} pnpm --filter macros migrate:production; fi"
|
|
4
4
|
publish = "templates/macros/dist"
|
|
5
5
|
functions = "templates/macros/.netlify/functions-internal"
|
|
6
6
|
|
|
@@ -16,6 +16,6 @@ AGENT_NATIVE_ENABLE_KEEP_WARM = "1"
|
|
|
16
16
|
AGENT_NATIVE_DISABLE_KEEP_WARM_BACKGROUND = "1"
|
|
17
17
|
# Macros owns its schema at release time (`migrate:production` above), so
|
|
18
18
|
# production request functions must not probe the database for migrations.
|
|
19
|
-
#
|
|
20
|
-
#
|
|
19
|
+
# Ordinary deploy previews still run request-path migrations; the beta
|
|
20
|
+
# prebuilt lane explicitly supplies the release owner.
|
|
21
21
|
AGENT_NATIVE_RELEASE_MIGRATIONS = "1"
|
|
@@ -5,6 +5,12 @@ import { getSetting } from "@agent-native/core/settings";
|
|
|
5
5
|
import { emailMessageMatchesSearch } from "@shared/search.js";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
|
|
8
|
+
import {
|
|
9
|
+
augmentSelfSentLabels,
|
|
10
|
+
filterInboxTabEmails,
|
|
11
|
+
OTHER_INBOX_TAB_PARAM,
|
|
12
|
+
resolvePinnedLabels,
|
|
13
|
+
} from "../app/lib/inbox-tabs.js";
|
|
8
14
|
import { buildGmailEmailSearchQuery } from "../server/lib/gmail-query.js";
|
|
9
15
|
import { gmailGetThread } from "../server/lib/google-api.js";
|
|
10
16
|
import {
|
|
@@ -16,10 +22,12 @@ import {
|
|
|
16
22
|
fetchGmailLabelMap,
|
|
17
23
|
} from "../server/lib/google-auth.js";
|
|
18
24
|
import { getSyntheticEmailsForView } from "../server/lib/jobs.js";
|
|
25
|
+
import { readSettings } from "../server/lib/mail-settings.js";
|
|
19
26
|
import {
|
|
20
27
|
listQueuedDrafts,
|
|
21
28
|
requireQueuedDraft,
|
|
22
29
|
} from "../server/lib/queued-drafts.js";
|
|
30
|
+
import type { EmailMessage } from "../shared/types.js";
|
|
23
31
|
import { getAccessTokens, fetchLabelMap } from "./helpers.js";
|
|
24
32
|
|
|
25
33
|
function latestPerThread(emails: any[]): any[] {
|
|
@@ -43,10 +51,33 @@ async function fetchEmailList(
|
|
|
43
51
|
view: string,
|
|
44
52
|
search?: string,
|
|
45
53
|
_label?: string,
|
|
54
|
+
activeInboxTab?: string,
|
|
55
|
+
activeAccounts?: string[],
|
|
46
56
|
): Promise<any[]> {
|
|
47
57
|
try {
|
|
48
58
|
const ownerEmail = getRequestUserEmail();
|
|
49
59
|
if (!ownerEmail) throw new Error("no authenticated user");
|
|
60
|
+
const shouldFilterOther =
|
|
61
|
+
view === "inbox" && !search && activeInboxTab === OTHER_INBOX_TAB_PARAM;
|
|
62
|
+
const selectedAccountEmails = Array.isArray(activeAccounts)
|
|
63
|
+
? [
|
|
64
|
+
...new Set(
|
|
65
|
+
activeAccounts
|
|
66
|
+
.filter((email): email is string => typeof email === "string")
|
|
67
|
+
.map((email) => email.toLowerCase()),
|
|
68
|
+
),
|
|
69
|
+
]
|
|
70
|
+
: [];
|
|
71
|
+
const selectedAccountSet = new Set(selectedAccountEmails);
|
|
72
|
+
const filterSelectedAccounts = (emails: any[]) => {
|
|
73
|
+
if (selectedAccountEmails.length === 0) return emails;
|
|
74
|
+
return emails.filter(
|
|
75
|
+
(email) =>
|
|
76
|
+
typeof email.accountEmail === "string" &&
|
|
77
|
+
selectedAccountSet.has(email.accountEmail.toLowerCase()),
|
|
78
|
+
);
|
|
79
|
+
};
|
|
80
|
+
|
|
50
81
|
if (view === "snoozed" || view === "scheduled") {
|
|
51
82
|
let emails = await getSyntheticEmailsForView(ownerEmail, view);
|
|
52
83
|
if (search) {
|
|
@@ -54,10 +85,37 @@ async function fetchEmailList(
|
|
|
54
85
|
emailMessageMatchesSearch(e, search),
|
|
55
86
|
);
|
|
56
87
|
}
|
|
57
|
-
return emails.slice(0, 50);
|
|
88
|
+
return filterSelectedAccounts(emails).slice(0, 50);
|
|
58
89
|
}
|
|
59
|
-
|
|
60
|
-
|
|
90
|
+
|
|
91
|
+
const googleConnected = await isConnected(ownerEmail);
|
|
92
|
+
const settings =
|
|
93
|
+
googleConnected || shouldFilterOther
|
|
94
|
+
? await readSettings(ownerEmail)
|
|
95
|
+
: undefined;
|
|
96
|
+
const userPinnedLabels = settings?.pinnedLabels;
|
|
97
|
+
const pinnedLabels =
|
|
98
|
+
userPinnedLabels === undefined
|
|
99
|
+
? resolvePinnedLabels([], googleConnected)
|
|
100
|
+
: resolvePinnedLabels(userPinnedLabels, googleConnected);
|
|
101
|
+
const hasNoteToSelf = pinnedLabels.includes("note-to-self");
|
|
102
|
+
const clients = googleConnected ? await getClients(ownerEmail) : [];
|
|
103
|
+
const connectedEmails = new Set(
|
|
104
|
+
clients.map(({ email }) => email.toLowerCase()),
|
|
105
|
+
);
|
|
106
|
+
const prepareEmails = (emails: any[]) => {
|
|
107
|
+
const augmented = augmentSelfSentLabels(emails as EmailMessage[], {
|
|
108
|
+
isGoogleConnected: googleConnected,
|
|
109
|
+
connectedEmails,
|
|
110
|
+
hasNoteToSelf,
|
|
111
|
+
});
|
|
112
|
+
return filterSelectedAccounts(augmented);
|
|
113
|
+
};
|
|
114
|
+
const applyActiveInboxTab = (emails: any[]) =>
|
|
115
|
+
shouldFilterOther
|
|
116
|
+
? filterInboxTabEmails(prepareEmails(emails), null, pinnedLabels)
|
|
117
|
+
: prepareEmails(emails);
|
|
118
|
+
if (googleConnected) {
|
|
61
119
|
const labelMap = new Map<string, string>();
|
|
62
120
|
await Promise.all(
|
|
63
121
|
clients.map(async ({ accessToken }) => {
|
|
@@ -79,6 +137,10 @@ async function fetchEmailList(
|
|
|
79
137
|
{
|
|
80
138
|
mode: "threads",
|
|
81
139
|
threadFormat: "metadata",
|
|
140
|
+
accountEmails:
|
|
141
|
+
selectedAccountEmails.length > 0
|
|
142
|
+
? selectedAccountEmails
|
|
143
|
+
: undefined,
|
|
82
144
|
threadCandidateLimit: search ? 500 : undefined,
|
|
83
145
|
threadRecentMessageCandidateLimit:
|
|
84
146
|
!search && (view === "inbox" || view === "unread")
|
|
@@ -87,9 +149,11 @@ async function fetchEmailList(
|
|
|
87
149
|
},
|
|
88
150
|
);
|
|
89
151
|
|
|
90
|
-
return
|
|
91
|
-
|
|
92
|
-
|
|
152
|
+
return applyActiveInboxTab(
|
|
153
|
+
latestPerThread(
|
|
154
|
+
messages.map((m: any) =>
|
|
155
|
+
gmailToEmailMessage(m, m._accountEmail, labelMap),
|
|
156
|
+
),
|
|
93
157
|
),
|
|
94
158
|
).slice(0, 50);
|
|
95
159
|
}
|
|
@@ -136,7 +200,7 @@ async function fetchEmailList(
|
|
|
136
200
|
emailMessageMatchesSearch(e, search),
|
|
137
201
|
);
|
|
138
202
|
}
|
|
139
|
-
return emails.slice(0, 50);
|
|
203
|
+
return applyActiveInboxTab(emails).slice(0, 50);
|
|
140
204
|
}
|
|
141
205
|
return [];
|
|
142
206
|
} catch {
|
|
@@ -255,7 +319,13 @@ export default defineAction({
|
|
|
255
319
|
};
|
|
256
320
|
}
|
|
257
321
|
} else if (nav?.view) {
|
|
258
|
-
const emails = await fetchEmailList(
|
|
322
|
+
const emails = await fetchEmailList(
|
|
323
|
+
nav.view,
|
|
324
|
+
nav.search,
|
|
325
|
+
nav.label,
|
|
326
|
+
nav.activeInboxTab,
|
|
327
|
+
nav.activeAccounts,
|
|
328
|
+
);
|
|
259
329
|
const selectedThreadIds = Array.isArray(nav.selectedThreadIds)
|
|
260
330
|
? new Set(
|
|
261
331
|
nav.selectedThreadIds.filter(
|
|
@@ -279,6 +349,8 @@ export default defineAction({
|
|
|
279
349
|
screen.emailList = {
|
|
280
350
|
view: nav.view,
|
|
281
351
|
label: nav.label ?? null,
|
|
352
|
+
activeInboxTab: nav.activeInboxTab ?? null,
|
|
353
|
+
activeAccounts: nav.activeAccounts ?? [],
|
|
282
354
|
search: nav.search ?? null,
|
|
283
355
|
selectedThreadIds: Array.from(selectedThreadIds),
|
|
284
356
|
count: compact.length,
|
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
useBulkToggleStar,
|
|
49
49
|
useBulkMarkRead,
|
|
50
50
|
useLabels,
|
|
51
|
+
EMPTY_LABELS,
|
|
51
52
|
useMoveEmail,
|
|
52
53
|
unsuppressThread,
|
|
53
54
|
} from "@/hooks/use-emails";
|
|
@@ -435,7 +436,8 @@ export function EmailList({
|
|
|
435
436
|
const bulkTrashEmails = useBulkTrashEmails();
|
|
436
437
|
const bulkToggleStar = useBulkToggleStar();
|
|
437
438
|
const bulkMarkRead = useBulkMarkRead();
|
|
438
|
-
const { data:
|
|
439
|
+
const { data: labelsData } = useLabels();
|
|
440
|
+
const labels = labelsData ?? EMPTY_LABELS;
|
|
439
441
|
const moveEmail = useMoveEmail();
|
|
440
442
|
const cancelScheduledJob = useDeleteScheduledJob();
|
|
441
443
|
const sendScheduledJobNow = useSendScheduledJobNow();
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
TooltipTrigger,
|
|
22
22
|
} from "@/components/ui/tooltip";
|
|
23
23
|
import { useAccountFilter } from "@/hooks/use-account-filter";
|
|
24
|
+
import { getLabelStyle } from "@/lib/label-colors";
|
|
24
25
|
import type { ThreadSummary } from "@/lib/threads";
|
|
25
26
|
import { cn, formatEmailDate, truncate } from "@/lib/utils";
|
|
26
27
|
|
|
@@ -96,29 +97,6 @@ function formatParticipants(participants: string[], maxWidth = 3): string {
|
|
|
96
97
|
return `${firstNames[0]} .. ${firstNames.slice(-(maxWidth - 1)).join(", ")}`;
|
|
97
98
|
}
|
|
98
99
|
|
|
99
|
-
// Map common label IDs to display colors
|
|
100
|
-
const labelColors: Record<string, { bg: string; text: string }> = {
|
|
101
|
-
automated: { bg: "bg-pink-500/20", text: "text-pink-700 dark:text-pink-300" },
|
|
102
|
-
social: { bg: "bg-blue-500/20", text: "text-blue-700 dark:text-blue-300" },
|
|
103
|
-
updates: {
|
|
104
|
-
bg: "bg-yellow-500/20",
|
|
105
|
-
text: "text-yellow-700 dark:text-yellow-300",
|
|
106
|
-
},
|
|
107
|
-
promotions: {
|
|
108
|
-
bg: "bg-green-500/20",
|
|
109
|
-
text: "text-green-700 dark:text-green-300",
|
|
110
|
-
},
|
|
111
|
-
forums: {
|
|
112
|
-
bg: "bg-sky-500/20",
|
|
113
|
-
text: "text-sky-700 dark:text-sky-300",
|
|
114
|
-
},
|
|
115
|
-
finance: {
|
|
116
|
-
bg: "bg-emerald-500/20",
|
|
117
|
-
text: "text-emerald-700 dark:text-emerald-300",
|
|
118
|
-
},
|
|
119
|
-
travel: { bg: "bg-cyan-500/20", text: "text-cyan-700 dark:text-cyan-300" },
|
|
120
|
-
};
|
|
121
|
-
|
|
122
100
|
/** Stable dot colors for distinguishing accounts */
|
|
123
101
|
const accountDotColors = [
|
|
124
102
|
"bg-blue-400",
|
|
@@ -139,18 +117,6 @@ function getAccountColor(
|
|
|
139
117
|
return accountDotColors[(idx >= 0 ? idx : 0) % accountDotColors.length];
|
|
140
118
|
}
|
|
141
119
|
|
|
142
|
-
function getLabelStyle(labelId: string): { bg: string; text: string } {
|
|
143
|
-
const normalized = labelId.toLowerCase().replace(/^label:/, "");
|
|
144
|
-
if (labelColors[normalized]) return labelColors[normalized];
|
|
145
|
-
// Fallback: hash to a color
|
|
146
|
-
let hash = 0;
|
|
147
|
-
for (let i = 0; i < normalized.length; i++) {
|
|
148
|
-
hash = normalized.charCodeAt(i) + ((hash << 5) - hash);
|
|
149
|
-
}
|
|
150
|
-
const options = Object.values(labelColors);
|
|
151
|
-
return options[Math.abs(hash) % options.length];
|
|
152
|
-
}
|
|
153
|
-
|
|
154
120
|
export const EmailListItem = memo(function EmailListItem({
|
|
155
121
|
email,
|
|
156
122
|
thread,
|
|
@@ -64,6 +64,7 @@ import {
|
|
|
64
64
|
decodeHtmlEntities,
|
|
65
65
|
processHtmlImages,
|
|
66
66
|
} from "@/lib/email-image-policy";
|
|
67
|
+
import { getLabelStyle } from "@/lib/label-colors";
|
|
67
68
|
import { isMcpEmbedSurface } from "@/lib/mcp-embed";
|
|
68
69
|
import { getResolvedTheme } from "@/lib/theme";
|
|
69
70
|
import { ensureThread, warmThreads } from "@/lib/thread-cache";
|
|
@@ -1138,14 +1139,21 @@ export function EmailThread({
|
|
|
1138
1139
|
<h1 className="text-base sm:text-lg font-semibold leading-tight text-foreground line-clamp-2">
|
|
1139
1140
|
{threadSubject}
|
|
1140
1141
|
</h1>
|
|
1141
|
-
{displayLabels.map((labelId) =>
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1142
|
+
{displayLabels.map((labelId) => {
|
|
1143
|
+
const style = getLabelStyle(labelId);
|
|
1144
|
+
return (
|
|
1145
|
+
<span
|
|
1146
|
+
key={labelId}
|
|
1147
|
+
className={cn(
|
|
1148
|
+
"label-badge shrink-0 mt-1",
|
|
1149
|
+
style.bg,
|
|
1150
|
+
style.text,
|
|
1151
|
+
)}
|
|
1152
|
+
>
|
|
1153
|
+
{labelId}
|
|
1154
|
+
</span>
|
|
1155
|
+
);
|
|
1156
|
+
})}
|
|
1149
1157
|
{/* Action bar */}
|
|
1150
1158
|
<div className="hidden sm:flex items-center gap-0.5 ml-auto shrink-0">
|
|
1151
1159
|
<Tooltip>
|