Create a new project through a chat-like conversational onboarding driven by Pseudo-Mara (a UI simulation, no LLM call). The wizard collects name, description, URL slug, accent color, and tech stack one bubble at a time, then atomically creates the project together with its first conversation. Requires a valid JWT access token. The project is created in DRAFT status and belongs to the authenticated user’s tenant.
Conversational onboarding (UX overview)
The user lands on the New Project screen and sees a chat-like timeline rather than a form.
Each step is a Pseudo-Mara bubble followed by an inline InteractivePrompt component
(input / textarea / chips). Pseudo-Mara is purely a frontend simulation — there is no
backend or LLM call until the final submit.
Step-by-step sequence:
- Greeting bubble —
i18n: createProject.greeting→ e.g."Ahoj {firstName}, dej mi pár základních informací."({firstName}resolved from the authenticated user’s profile). - Name — inline
InteractivePromptof typeinput, mandatory (max 100 chars). The Next button stays disabled until the user types a non-blank value. - Description — inline
InteractivePromptof typetextarea, optional (max 500 chars). Counter shown; over-limit blocks Next. - URL slug — inline
InteractivePromptof typeinputwith a fixed suffix.talkide.apprendered next to the field. The user only types the slug part. The FE auto-prefills the slug from the project name (kebab-cased), but the user can override it. As the user types, the FE debounces input by 400 ms and callsGET /api/v1/projects/check-url?url=<slug>.talkide.app. Until the slug is valid AND available, Next stays disabled. An inline status chip showschecking…,available,taken, orinvalid format. - Accent — inline
InteractivePromptof typechipswith the 6 oklch presets of the design system (the legacy custom color picker has been removed). Mandatory. Default selection: the first preset. - Tech-stack offer — bubble:
"Použijeme technologie se kterými máme dlouhodobě nejlepší výsledky?"followed by 2 chips:ANO (doporučeno)— the FE submits without sending any tech-stack fields; the BE fills in defaults (Kotlin / Spring Boot / TypeScript / Vue.js).Vyberu si svůj tech stack— opens 4 additional inline chip prompts, in order:- Backend language —
Kotlin (doporučeno)/Java/TypeScript/Python - Backend framework —
Spring Boot (doporučeno)/Quarkus/NestJS/Django - Frontend language —
TypeScript (doporučeno)/JavaScript - Frontend framework —
Vue.js (doporučeno)/React/Svelte
- Backend language —
After the tech-stack step the FE submits POST /api/v1/projects. The BE atomically:
- creates the project (status =
DRAFT), - immediately starts the first conversation via
StartConversationUseCase(no user message yet — see UC-04002, “auto-start variant”), - returns the new
projectIdandconversationIdin the same response.
The FE then routes to /workspace/:projectId, loads the conversation, and lets the real Mara
(LLM, streamed via SSE) deliver an opening message without any user input. Mara uses
{{project.description}} from her rendered context as the situational anchor; she does not
greet the user (Pseudo-Mara already did) and asks a short, focused question about today’s work.
Notes
- The URL must be unique across all projects globally (not just within the tenant), end with
.talkide.app, and contain only lowercase alphanumeric characters and hyphens in the subdomain part. - The
accentfield is mandatory and must be one of the 6 oklch presets defined in the design system. - Tech-stack fields are optional in the request DTO. If omitted (recommended path), the BE fills in the team defaults:
Kotlin / Spring Boot / TypeScript / Vue.js. - The legacy single
techStackstring field has been removed in favour of 4 structured fields. The DB columntech_stackis dropped and replaced bybackend_language,backend_framework,frontend_language,frontend_framework. - Project + first conversation are created in a single transaction. If conversation creation fails, the project is rolled back (no orphan project without a conversation).
sequenceDiagram
actor User
User->>+FE: opens New Project screen
FE-->>User: Pseudo-Mara: greeting bubble (i18n)
User->>FE: types Name (inline input)
FE->>FE: validate Name (not blank, max 100)
User->>FE: types Description (inline textarea)
FE->>FE: validate Description (max 500)
User->>FE: types URL slug (inline input + .talkide.app suffix)
loop debounce 400 ms while typing
FE->>+BE: GET /api/v1/projects/check-url?url={slug}.talkide.app <br> Authorization: Bearer {accessToken}
BE->>DB: check if URL is already taken
BE->>-FE: 200 OK <br> UrlAvailabilityResponse
FE-->>User: show availability chip (checking / available / taken / invalid)
end
User->>FE: picks Accent (one of 6 oklch chips)
FE-->>User: Pseudo-Mara: tech-stack offer bubble + 2 chips
alt user picks "ANO (doporučeno)"
FE->>FE: skip tech-stack questions
else user picks "Vyberu si svůj tech stack"
User->>FE: picks BE language
User->>FE: picks BE framework
User->>FE: picks FE language
User->>FE: picks FE framework
end
FE->>+BE: POST /api/v1/projects <br> Authorization: Bearer {accessToken} <br> CreateProjectRequest
BE->>BE: validate request
alt request is invalid
BE-->>FE: 400 Bad Request <br> ErrorResponse
end
BE->>DB: check if URL is already taken
alt URL already taken
BE-->>FE: 409 Conflict <br> ErrorResponse
end
BE->>BE: apply tech-stack defaults for omitted fields
BE->>DB: insert project (status = DRAFT)
BE->>BE: StartConversationUseCase (auto-start, no first message)
BE->>DB: insert conversation (status = ACTIVE, title = "New project")
BE->>-FE: 201 Created <br> CreateProjectResponse (includes conversationId)
FE->>FE: route to /workspace/:projectId
FE->>+BE: GET /api/v1/projects/{projectId}/conversations/{conversationId}
BE->>-FE: 200 OK <br> ConversationDetailResponse
FE->>+BE: POST /api/v1/projects/{projectId}/conversations/{conversationId}/auto-start <br> (SSE)
BE->>BE: spawn Mara (LLM) with full context (incl. project.description)
BE-->>FE: SSE stream — Mara opening message (no greeting, asks what we work on)
BE->>-FE: SSE end
FE-->>-User: workspace ready, Mara has spoken
Check URL Availability
GET /api/v1/projects/check-url?url=wildwood-bakery.talkide.app (no request body)
200 OK UrlAvailabilityResponse:
{
"data": {
"available": true
}
}
400 Bad Request (invalid URL format) ErrorResponse:
{
"status": 400,
"code": "VALIDATION_ERROR",
"message": "Validation failed",
"errors": [
{ "field": "url", "message": "must end with .talkide.app and contain only lowercase alphanumeric characters and hyphens" }
]
}
401 Unauthorized (missing or invalid access token) ErrorResponse:
{
"status": 401,
"code": "AUTHENTICATION_FAILED",
"message": "Access token is missing or invalid"
}
Create Project
POST /api/v1/projects CreateProjectRequest (recommended-defaults variant):
{
"name": "Wildwood Bakery",
"description": "Online ordering for a neighborhood bakery",
"url": "wildwood-bakery.talkide.app",
"accent": "oklch(0.78 0.15 70)"
}
POST /api/v1/projects CreateProjectRequest (custom tech-stack variant):
{
"name": "Wildwood Bakery",
"description": "Online ordering for a neighborhood bakery",
"url": "wildwood-bakery.talkide.app",
"accent": "oklch(0.78 0.15 70)",
"backendLanguage": "Java",
"backendFramework": "Quarkus",
"frontendLanguage": "TypeScript",
"frontendFramework": "React"
}
201 Created CreateProjectResponse:
{
"data": {
"id": 1,
"conversationId": 8,
"name": "Wildwood Bakery",
"description": "Online ordering for a neighborhood bakery",
"status": "DRAFT",
"url": "wildwood-bakery.talkide.app",
"accent": "oklch(0.78 0.15 70)",
"backendLanguage": "Kotlin",
"backendFramework": "Spring Boot",
"frontendLanguage": "TypeScript",
"frontendFramework": "Vue.js",
"progress": null,
"createdAt": "2026-05-03T10:00:00Z",
"updatedAt": "2026-05-03T10:00:00Z"
}
}
400 Bad Request (validation) ErrorResponse:
{
"status": 400,
"code": "VALIDATION_ERROR",
"message": "Validation failed",
"errors": [
{ "field": "name", "message": "must not be blank" }
]
}
401 Unauthorized (missing or invalid access token) ErrorResponse:
{
"status": 401,
"code": "AUTHENTICATION_FAILED",
"message": "Access token is missing or invalid"
}
409 Conflict (URL already taken) ErrorResponse:
{
"status": 409,
"code": "CONFLICT_PROJECT",
"message": "URL is already taken by another project"
}
Frontend
Validations
| Field | Constraints | Size | Pattern | Note |
|---|---|---|---|---|
| name | not_blank | 1 - 100 | Mandatory; Next disabled until non-blank | |
| description | (optional) | 0 - 500 | Counter shown inline | |
| url | not_blank | 1 - 253 | ^[a-z0-9-]+\.talkide\.app$ | Auto-prefilled from name slug; live availability check (400 ms debounce); Next disabled until valid AND available |
| accent | not_blank | — | One of the 6 oklch presets defined in the design system | |
| backendLanguage | (optional) | — | One of: Kotlin / Java / TypeScript / Python (only sent if user picked custom path) | |
| backendFramework | (optional) | — | One of: Spring Boot / Quarkus / NestJS / Django | |
| frontendLanguage | (optional) | — | One of: TypeScript / JavaScript | |
| frontendFramework | (optional) | — | One of: Vue.js / React / Svelte |
Pseudo-Mara behaviour
- Pseudo-Mara is rendered as a chat timeline; each step adds a new bubble + inline
InteractivePrompt. - No backend/LLM calls during the wizard, except the URL availability check.
- Each user response is rendered as a user bubble below Pseudo-Mara’s question (mirrors the real chat experience).
- “Back” / edit-previous-step is allowed: clicking a previous answer collapses subsequent steps and re-opens that step.
i18nkeys live undercreateProject.*(e.g.createProject.greeting,createProject.askName,createProject.askTechStackOffer,createProject.recommendedYes,createProject.recommendedNo).
Backend
Validations
| Field | Constraints | Size | Pattern | Note |
|---|---|---|---|---|
| name | not_blank | 1 - 100 | ||
| description | (optional) | 0 - 500 | ||
| url | not_blank | 1 - 253 | ^[a-z0-9-]+\.talkide\.app$ | Must be globally unique |
| accent | not_blank, in_set | — | Must match one of the 6 oklch presets | |
| backendLanguage | (optional), in_set | — | If null → defaults to Kotlin | |
| backendFramework | (optional), in_set | — | If null → defaults to Spring Boot | |
| frontendLanguage | (optional), in_set | — | If null → defaults to TypeScript | |
| frontendFramework | (optional), in_set | — | If null → defaults to Vue.js |
Schema changes (Liquibase, in-place edit allowed — pre-production)
| Change | Detail |
|---|---|
| DROP | project.tech_stack (VARCHAR) |
| ADD | project.backend_language VARCHAR NOT NULL DEFAULT 'Kotlin' |
| ADD | project.backend_framework VARCHAR NOT NULL DEFAULT 'Spring Boot' |
| ADD | project.frontend_language VARCHAR NOT NULL DEFAULT 'TypeScript' |
| ADD | project.frontend_framework VARCHAR NOT NULL DEFAULT 'Vue.js' |
The entity model in model/README.md is updated accordingly.
Atomic project + conversation creation
CreateProjectUseCase orchestrates both steps in a single transaction:
- Insert
project(status =DRAFT). - Call
StartConversationUseCase.autoStart(projectId)— creates a conversation (status =ACTIVE, title ="New project", no first user message,sessionId = null). - Return
CreateProjectResponsewithid(project) ANDconversationId.
If step 2 fails for any reason, the whole transaction rolls back — no orphan project.
The auto-start variant is described in UC-04002.
Mara context (mara-context.md) — required updates by BE
The <project-path>/CLAUDE.md template rendered by BE (see specification/mara-context.md) MUST be updated to support the conversational create flow:
- BUG FIX — the current template does not include
{{project.description}}. Mara therefore never receives the project description as context. Add aPopisline underProjekt. - Replace
{{project.techStack}}(currently absent in the template, but planned) with four structured lines for the new fields, e.g.:- **Backend**: {`{`}{`{`}project.backendLanguage{`}`}{`}`} / {`{`}{`{`}project.backendFramework{`}`}{`}`} - **Frontend**: {`{`}{`{`}project.frontendLanguage{`}`}{`}`} / {`{`}{`{`}project.frontendFramework{`}`}{`}`} - Welcome instruction — add an explicit section instructing Mara:
Pseudo-Mara již proběhla a uživatele přivítala. Nezdrav. Stručně se zeptej, na čem dnes budeme pracovat — využij popis projektu jako kontext.
These changes preserve determinism (pure projection of stable DB state — no timestamps, no nonces) and therefore do not break the prompt cache contract.
Test Cases
| GIVEN | WHEN | THEN |
|---|---|---|
| authenticated user, valid request with recommended defaults (no tech-stack fields) | POST /projects is called | 201 Created; project in DRAFT; tech-stack defaults applied (Kotlin/Spring Boot/TypeScript/Vue.js); response contains conversationId of the auto-started conversation |
| authenticated user, valid request with custom tech-stack (all 4 fields) | POST /projects is called | 201 Created; project in DRAFT; supplied tech-stack values stored as-is; response contains conversationId |
| authenticated user, valid request with description | POST /projects is called | 201 Created; description stored on project; later renders into Mara’s CLAUDE.md context |
| authenticated user, valid request with only some tech-stack fields | POST /projects is called | 201 Created; missing tech-stack fields filled with defaults |
| authenticated user, URL already taken by another project | POST /projects is called | 409 CONFLICT_PROJECT error response is returned; no project and no conversation created |
| name is blank | POST /projects is called | 400 VALIDATION_ERROR error response is returned |
| name longer than 100 characters | POST /projects is called | 400 VALIDATION_ERROR error response is returned |
| description longer than 500 characters | POST /projects is called | 400 VALIDATION_ERROR error response is returned |
| URL does not end with .talkide.app | POST /projects is called | 400 VALIDATION_ERROR error response is returned |
| URL contains uppercase characters | POST /projects is called | 400 VALIDATION_ERROR error response is returned |
| accent is blank | POST /projects is called | 400 VALIDATION_ERROR error response is returned |
| accent is not one of the 6 oklch presets | POST /projects is called | 400 VALIDATION_ERROR error response is returned |
| backendLanguage is not in allowed set | POST /projects is called | 400 VALIDATION_ERROR error response is returned |
| backendFramework / frontendLanguage / frontendFramework not in allowed set | POST /projects is called | 400 VALIDATION_ERROR error response is returned |
| auto-conversation creation fails (simulated DB error in StartConversationUseCase) | POST /projects is called | transaction rolls back; no project, no conversation persisted; 5xx returned |
| invalid request (empty body) | POST /projects is called | 400 VALIDATION_ERROR error response is returned |
| no Authorization header | POST /projects is called | 401 AUTHENTICATION_FAILED error response is returned |
| available URL | GET /projects/check-url?url=fresh-name.talkide.app is called | 200 OK with available=true |
| URL already taken | GET /projects/check-url?url=taken-name.talkide.app is called | 200 OK with available=false |
| URL with invalid format | GET /projects/check-url?url=INVALID is called | 400 VALIDATION_ERROR error response is returned |
| no Authorization header | GET /projects/check-url is called | 401 AUTHENTICATION_FAILED error response is returned |
Frontend Test Cases (component / e2e)
| GIVEN | WHEN | THEN |
|---|---|---|
| user opens New Project screen | greeting bubble renders | {firstName} is interpolated from current user profile |
| user has not yet typed a name | clicks Next on Name step | Next is disabled (no submit attempt) |
| user typed slug “wildwood-bakery”, BE returns available=true | 400 ms debounce elapses | availability chip shows “available”; Next becomes enabled |
| user typed slug “wildwood-bakery”, BE returns available=false | 400 ms debounce elapses | availability chip shows “taken”; Next stays disabled |
| user typed slug “INVALID” (uppercase) | format validation runs | availability chip shows “invalid format”; no BE call is fired |
| user picks “ANO (doporučeno)” on tech-stack offer | submit fires | request body omits the 4 tech-stack fields |
| user picks “Vyberu si svůj tech stack” | flow continues | 4 additional chip prompts appear in order; submit fires only after all 4 are answered |
BE returns 201 with conversationId = 8 | navigation runs | FE routes to /workspace/{projectId} and loads conversation 8 |
| workspace loaded, conversation has zero messages | auto-start streams | Mara’s opening SSE message arrives without any user input; the message does not contain a greeting (e.g. no “Ahoj”) and references the project description |
Thanks for the feedback.