Internal Documentation internal
TalkIDE internal documentation

Tenant-scoped cross-project activity feed displayed on the Studio page. Shows the 10 most recent activities across all of the current user’s projects, with real-time updates pushed via SSE.

  • The feed is tenant-scoped — the user sees activities only from projects that belong to their tenant.
  • Initial snapshot is fetched via REST on Studio page mount; real-time updates arrive over SSE without polling.
  • The Pinia store (studioActivity) caps the in-memory list at 50 entries — the oldest items are dropped as new SSE events arrive.
  • Each activity row is clickable and navigates the user to the Workspace of the corresponding project.
  • This UC is the tenant-level counterpart to the per-project Workspace Activity Feed (UC-05001).

Relationship to UC-05001

DimensionUC-05001 — Workspace ActivityUC-06001 — Studio Recent Activity
ScopeSingle projectAll projects of current tenant
Endpoint/api/v1/projects/{projectId}/activities/api/v1/studio/recent-activity
SSE endpoint/api/v1/projects/{projectId}/activities/stream/api/v1/studio/recent-activity/stream
DTOActivityDtoStudioActivityDto (adds projectId, projectName)
Navigation on row clickstays in workspacenavigates to workspace of that project
Store capno cap documented50 entries

Data Entity — StudioActivityDto

StudioActivityDto is an enriched version of ActivityDto that adds tenant-level context fields.

Location: talkide-be/src/main/kotlin/com/mddsummer/talkide/features/activity/api/dto/StudioActivityDto.kt

FieldTypeNullableDescription
idLongNOT NULLActivity primary key
projectIdLongNOT NULLFK → projects(id); identifies which project this activity belongs to
projectNameStringNOT NULLHuman-readable project name; resolved from Caffeine cache (projectId → tenantId + projectName) at publish time
conversationIdLongNOT NULLFK → conversations(id)
agentRoleStringNOT NULLTechnical agent key — same values as ActivityDto.agentRole (e.g. talkide-frontend-dev)
eventTypeStringNOT NULLTASK_STARTED | TASK_COMPLETED | TOOL_USE | AGENT_MESSAGE
parentActivityIdLongNULLFK → activities(id) self-reference; same semantics as ActivityDto
parentActivityDescriptionStringNULLDescription parent activity (pokud parentActivityId je set a parent existuje); null pokud activity nemá parent nebo parent neexistuje
descriptionStringNULLUser-facing label; set for TASK_STARTED and TASK_COMPLETED
toolNameStringNULLRaw tool name (e.g. Read, Write, Bash); set only for TOOL_USE
toolCategoryString (enum)NULLBusiness category: READING | EDITING | EXECUTING | DELEGATING | BROWSING | OTHER; set only for TOOL_USE; null for TASK_STARTED, TASK_COMPLETED, AGENT_MESSAGE
toolSummaryStringNULLExtracted tool summary or first 200 chars of agent message
createdAtString (ISO 8601)NOT NULLTimestamp when the activity was created

payloadJson is intentionally excluded from the response (internal detail, same policy as UC-05001).

Tenant Isolation — Caffeine Cache

ActivityEventBus maintains a Caffeine cache keyed by projectId mapping to (tenantId, projectName). On dual-publish (project-level + tenant-level), the publisher looks up the cache to enrich the event with projectName. Requests to /api/v1/studio/recent-activity filter by the tenantId extracted from the JWT.

REST — Initial Snapshot

sequenceDiagram
    actor User

    User->>+FE: opens Studio page

    FE->>+BE: GET /api/v1/studio/recent-activity?limit=10 <br> Authorization: Bearer {accessToken}

    BE->>BE: validate access token
    alt token missing or invalid
        BE-->>FE: 401 Unauthorized <br> ErrorResponse
    end

    BE->>DB: query activities WHERE tenant_id = ? ORDER BY created_at DESC LIMIT ?

    BE->>-FE: 200 OK <br> StudioActivityDto[]

    FE->>-User: render initial activity list (newest first)

GET /api/v1/studio/recent-activity?limit=10

Query parameters:

ParameterRequiredDefaultMinMaxDescription
limitno101100Number of activities to return

200 OK StudioActivityDto[]:

[
  {
    "id": 12346,
    "projectId": 7,
    "projectName": "My SaaS App",
    "conversationId": 42,
    "agentRole": "talkide-frontend-dev",
    "eventType": "TASK_COMPLETED",
    "parentActivityId": null,
    "parentActivityDescription": null,
    "description": "Pozadí aplikace",
    "toolName": null,
    "toolCategory": null,
    "toolSummary": null,
    "createdAt": "2026-04-30T11:24:30Z"
  },
  {
    "id": 12345,
    "projectId": 7,
    "projectName": "My SaaS App",
    "conversationId": 42,
    "agentRole": "talkide-frontend-dev",
    "eventType": "TASK_STARTED",
    "parentActivityId": null,
    "parentActivityDescription": null,
    "description": "Pozadí aplikace",
    "toolName": null,
    "toolCategory": null,
    "toolSummary": null,
    "createdAt": "2026-04-30T11:24:00Z"
  }
]

401 Unauthorized (missing or invalid access token) ErrorResponse:

{
  "status": 401,
  "code": "AUTHENTICATION_FAILED",
  "message": "Access token is missing or invalid"
}

SSE — Live Stream

sequenceDiagram
    actor User

    User->>+FE: Studio page mounted

    FE->>+BE: GET /api/v1/studio/recent-activity/stream <br> Authorization: Bearer {accessToken}

    BE->>BE: validate access token
    alt token missing or invalid
        BE-->>FE: 401 Unauthorized <br> ErrorResponse
    end

    BE-->>FE: SSE event: connected

    loop any project in tenant emits a new activity
        BE->>BE: ActivityEventBus publishes to tenant-level channel
        BE-->>FE: SSE event: activity <br> StudioActivityDto
    end

    loop every 30 seconds
        BE-->>FE: SSE event: heartbeat
    end

    FE->>-User: activity list updated in real time (prepend, cap 50)

GET /api/v1/studio/recent-activity/stream

Authorization: Bearer token in Authorization header (native EventSource does not support custom headers — FE uses a fetch-based SSE parser). Response content type: text/event-stream.

For the full SSE event-name convention and reconnect strategy, see architecture.md — SSE Convention.

SSE event: connected — stream established:

{}

SSE event: activity — a new activity was published to the tenant channel:

{
  "id": 12347,
  "projectId": 3,
  "projectName": "E-commerce Backend",
  "conversationId": 17,
  "agentRole": "talkide-backend-dev",
  "eventType": "TASK_STARTED",
  "parentActivityId": null,
  "parentActivityDescription": null,
  "description": "Implementace platební brány",
  "toolName": null,
  "toolCategory": null,
  "toolSummary": null,
  "createdAt": "2026-04-30T12:00:00Z"
}

SSE event: heartbeat — keep-alive pulse every 30 s:

{}

401 Unauthorized (missing or invalid access token) ErrorResponse:

{
  "status": 401,
  "code": "AUTHENTICATION_FAILED",
  "message": "Access token is missing or invalid"
}

UX Notes

  • Row click navigates to the Workspace of the activity’s project (uses projectId).
  • Project name (projectName) is displayed alongside the agent role and description so the user can distinguish which project the activity belongs to.
  • Server-side deduplication: the REST snapshot (GET /api/v1/studio/recent-activity) returns a BE pre-deduped list — consecutive activities sharing the same (tool_category, agent_role, parent_activity_id) key are coalesced by the repository query (window function LAG() / “gaps and islands” pattern). FE does not perform any own dedup logic; it is a pure renderer.
  • Tool call rendering: pro tool call activity (TOOL_USE eventType s parentActivityId) FE zobrazuje text formátu "{toolCategory} pro {parentActivityDescription}", kde toolCategory je lokalizovaný název kategorie nástroje z ToolCategory enumu (např. “Reading files” pro READING). Pokud parentActivityDescription je null, fallback na toolSummary. Pokud i to chybí, zobrazí se jen kategorie bez ” pro …”.
  • Store cap: the studioActivity Pinia store prepends incoming SSE activity events and trims the list to 50 entries. The REST snapshot replaces the list on mount.
  • No polling: the SSE stream keeps the feed live. Reconnect uses exponential backoff (see architecture.md).
  • The Studio page connects the SSE stream on mount and disconnects on unmount.

Time-of-day Greeting

The Studio page displays a personalized greeting in the hero/header area. The greeting is computed entirely on the frontend — no backend call is needed.

Logic

The greeting phrase is selected based on new Date().getHours() at the time the Studio page renders:

Hour rangeCS phraseEN phrase
5:00 – 10:59Dobré ráno, {salutation}Good morning, {salutation}
11:00 – 12:59Dobrý den, {salutation}Hi, {salutation}
13:00 – 17:59Dobré odpoledne, {salutation}Good afternoon, {salutation}
18:00 – 4:59Dobrý večer, {salutation}Good evening, {salutation}

Name substitution

  • {salutation} is sourced from userProfile.salutation (loaded via GET /api/v1/users/me).
  • If salutation is null or empty string, the greeting is rendered without a name (fallback variant — see i18n keys below).

i18n keys

Namespace: studio.greeting — 4 time-of-day keys × 2 variants (with name / without name) = 8 keys:

KeyValue (CS)Value (EN)
studio.greeting.morningDobré ráno, {name}!Good morning, {name}!
studio.greeting.morning_no_nameDobré ráno!Good morning!
studio.greeting.middayDobrý den, {name}!Hi, {name}!
studio.greeting.midday_no_nameDobrý den!Hi!
studio.greeting.afternoonDobré odpoledne, {name}!Good afternoon, {name}!
studio.greeting.afternoon_no_nameDobré odpoledne!Good afternoon!
studio.greeting.eveningDobrý večer, {name}!Good evening, {name}!
studio.greeting.evening_no_nameDobrý večer!Good evening!

Frontend rendering notes

  • The greeting is a pure computed property / composable — no watcher, no API call.
  • Re-evaluates when userProfile is loaded (or reloaded after profile update).
  • The greeting does not automatically update while the user is on the Studio page (no timer); it reflects the hour at the time of page load/navigation.

Frontend

Validations

FieldConstraintsSizePatternNote
limit(optional)1 – 100Defaults to 10; clamped to 100 max

Backend

Validations

FieldConstraintsSizePatternNote
limitmin=1, max=100Default 10; extracted from tenantId in JWT — no path variable needed

Test Cases

GIVENWHENTHEN
Authenticated user, tenant has 2 projects each with 15 activitiesGET /api/v1/studio/recent-activity?limit=10 is called200 OK with 10 most recent activities across both projects, ordered by created_at DESC
Authenticated user, tenant has no activitiesGET /api/v1/studio/recent-activity is called200 OK with empty array
limit=5 providedGET /api/v1/studio/recent-activity?limit=5 is called200 OK with at most 5 activities
limit=200 (exceeds max)GET /api/v1/studio/recent-activity?limit=200 is called400 Bad Request with validation error
No Authorization headerGET /api/v1/studio/recent-activity is called401 AUTHENTICATION_FAILED error response
Authenticated user, SSE stream open, any project in tenant emits a new activityActivityEventBus publishes to tenant channelSSE event activity with StudioActivityDto (including projectName) is pushed to connected client
No Authorization headerGET /api/v1/studio/recent-activity/stream is called401 AUTHENTICATION_FAILED error response
Activities from another tenant exist in DBGET /api/v1/studio/recent-activity is calledCross-tenant activities are NOT included in the response

Was this page helpful?

Thanks for the feedback.