diff --git a/src/app/new/form-reducer.test.ts b/src/app/new/form-reducer.test.ts index 1b2949f9..d5ac6ed3 100644 --- a/src/app/new/form-reducer.test.ts +++ b/src/app/new/form-reducer.test.ts @@ -1,5 +1,6 @@ import { describe, it, expect } from 'vitest'; import { formReducer, initialFormState } from './form-reducer'; +import { SESSION_NAME_MAX_LENGTH } from '@/lib/types'; import type { FormState } from './form-reducer'; import type { Repo } from '@/components/RepoSelector'; import type { Issue } from '@/lib/types'; @@ -204,6 +205,25 @@ describe('formReducer', () => { expect(result.initialPrompt).toBe(''); }); + it('truncates session name to max length when issue title is very long', () => { + const longTitle = 'A'.repeat(200); + const longIssue: Issue = { + ...mockIssue, + number: 1, + title: longTitle, + }; + const state: FormState = { + ...initialFormState, + selectedRepo: mockRepo, + selectedBranch: 'main', + }; + + const result = formReducer(state, { type: 'selectIssue', issue: longIssue }); + + expect(result.sessionName.length).toBe(SESSION_NAME_MAX_LENGTH); + expect(result.sessionName).toBe(`#1: ${longTitle}`.slice(0, SESSION_NAME_MAX_LENGTH)); + }); + it('preserves session name when issue is deselected and name was manually edited', () => { const state: FormState = { ...initialFormState, diff --git a/src/app/new/form-reducer.ts b/src/app/new/form-reducer.ts index a089b76a..cc19df07 100644 --- a/src/app/new/form-reducer.ts +++ b/src/app/new/form-reducer.ts @@ -1,5 +1,5 @@ import type { Repo } from '@/components/RepoSelector'; -import type { Issue } from '@/lib/types'; +import { SESSION_NAME_MAX_LENGTH, type Issue } from '@/lib/types'; export interface FormState { selectedRepo: Repo | null; @@ -44,7 +44,7 @@ export function formReducer(state: FormState, action: FormAction): FormState { sessionName: state.nameManuallyEdited ? state.sessionName : action.issue - ? `#${action.issue.number}: ${action.issue.title}` + ? `#${action.issue.number}: ${action.issue.title}`.slice(0, SESSION_NAME_MAX_LENGTH) : '', initialPrompt: state.promptManuallyEdited ? state.initialPrompt diff --git a/src/app/new/page.tsx b/src/app/new/page.tsx index c0f8eaf7..0b22ab3b 100644 --- a/src/app/new/page.tsx +++ b/src/app/new/page.tsx @@ -18,6 +18,7 @@ import { BranchSelector } from '@/components/BranchSelector'; import { IssueSelector } from '@/components/IssueSelector'; import type { Repo } from '@/components/RepoSelector'; import type { Issue } from '@/lib/types'; +import { SESSION_NAME_MAX_LENGTH } from '@/lib/types'; import { formReducer, initialFormState } from './form-reducer'; function generateIssuePrompt(issue: Issue, repoFullName: string): string { @@ -115,9 +116,9 @@ function NewSessionForm() { return; } - const defaultName = isNoRepo - ? 'Workspace' - : `${form.selectedRepo.name} - ${form.selectedBranch}`; + const defaultName = ( + isNoRepo ? 'Workspace' : `${form.selectedRepo.name} - ${form.selectedBranch}` + ).slice(0, SESSION_NAME_MAX_LENGTH); createMutation.mutate({ name: form.sessionName || defaultName, @@ -164,6 +165,7 @@ function NewSessionForm() { type="text" value={form.sessionName} onChange={handleNameChange} + maxLength={SESSION_NAME_MAX_LENGTH} placeholder={ isNoRepo ? 'Workspace' diff --git a/src/lib/types.ts b/src/lib/types.ts index 754c5b97..44e373db 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -19,6 +19,9 @@ export const MessageType = { export type MessageType = (typeof MessageType)[keyof typeof MessageType]; +// Session constants +export const SESSION_NAME_MAX_LENGTH = 100; + // Session interface export interface Session { id: string; diff --git a/src/server/routers/sessions.ts b/src/server/routers/sessions.ts index d20abfb3..4fcaa6f1 100644 --- a/src/server/routers/sessions.ts +++ b/src/server/routers/sessions.ts @@ -13,6 +13,7 @@ import { runClaudeCommand, stopSession } from '../services/claude-runner'; import { sseEvents } from '../services/events'; import { createLogger, toError } from '@/lib/logger'; import { env } from '@/lib/env'; +import { SESSION_NAME_MAX_LENGTH } from '@/lib/types'; const log = createLogger('sessions'); @@ -111,7 +112,7 @@ export const sessionsRouter = router({ create: protectedProcedure .input( z.object({ - name: z.string().min(1).max(100), + name: z.string().min(1).max(SESSION_NAME_MAX_LENGTH), repoFullName: z .string() .regex(/^[\w-]+\/[\w.-]+$/)