Skip to content

copilot connect UI polish#1052

Merged
sfierro merged 1 commit into
sfierro/optimize-featurefrom
sfierro/copilot-connect-polish
Feb 19, 2026
Merged

copilot connect UI polish#1052
sfierro merged 1 commit into
sfierro/optimize-featurefrom
sfierro/copilot-connect-polish

Conversation

@sfierro

@sfierro sfierro commented Feb 19, 2026

Copy link
Copy Markdown
Contributor

No description provided.

@sfierro sfierro changed the base branch from main to sfierro/optimize-feature February 19, 2026 02:09
@coderabbitai

coderabbitai Bot commented Feb 19, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

This PR introduces a comprehensive Prompt Optimization workflow with entitlements checking. Key additions include a new prompt_optimization_job_api module with seven endpoints for job management, a check_entitlements API for feature gating, new server-side endpoints for updating evals and run configs, multiple new Svelte pages/components for the optimize UI flow, migrated API client models from GEPA to PromptOptimization nomenclature, and corresponding data models for PromptOptimizationJob.

Changes

Cohort / File(s) Summary
Desktop Server Initialization
app/desktop/desktop_server.py
Registers the new prompt optimization job API via connect_prompt_optimization_job_api(app) during app initialization.
Prompt Optimization Job API
app/desktop/studio_server/prompt_optimization_job_api.py
Comprehensive new module (721 lines) implementing seven API endpoints (check_run_config, check_eval, start, list, get, status, result) with orchestration logic for job creation, status polling, and artifact materialization (prompts and run configs).
Settings & Entitlements APIs
app/desktop/studio_server/settings_api.py, app/desktop/studio_server/eval_api.py
Added /api/check_entitlements endpoint for feature-code entitlement checking; added PATCH endpoints for updating eval (name, description, train_set_filter_id) and run config (name, starred, prompt_name); extended EvalProgress with train_dataset_size field.
Response Handling Utilities
app/desktop/studio_server/utils/response_utils.py, app/desktop/studio_server/copilot_api.py, app/desktop/studio_server/utils/copilot_utils.py
New centralized response unwrapping utilities (check_response_error, unwrap_response, unwrap_response_allow_none) replacing inline HTTPValidationError checks across copilot and settings APIs.
API Client — Auth & Health Endpoints
app/desktop/studio_server/api_client/kiln_ai_server_client/api/auth/check_entitlements_v1_check_entitlements_get.py, app/desktop/studio_server/api_client/kiln_ai_server_client/api/auth/*.py, app/desktop/studio_server/api_client/kiln_ai_server_client/api/health/*.py
New check_entitlements API client module (198 lines) with sync/async wrappers; minor formatting updates to existing auth and health endpoint files.
API Client — GEPA → PromptOptimization Migration
app/desktop/studio_server/api_client/kiln_ai_server_client/api/jobs/check_prompt_optimization_model_supported_*.py, .../get_prompt_optimization_job_result_*.py, .../start_prompt_optimization_job_*.py
Replaced GEPA endpoint paths and response models with PromptOptimization equivalents; updated function signatures for sync/async detailed/convenience wrappers to reflect new request/response types (BodyStartPromptOptimizationJobV1... and PromptOptimizationJobResultResponse).
API Client — Models
app/desktop/studio_server/api_client/kiln_ai_server_client/models/__init__.py, .../models/body_start_prompt_optimization_job_*.py, .../models/prompt_optimization_job_*.py, .../models/check_entitlements_*.py
Removed GEPA types (BodyStartGepaJobV1..., GEPAJobOutput, GEPAJobResultResponse); added PromptOptimization equivalents; added new CheckEntitlementsV1... model; updated public exports in init.py.
Core Data Models
libs/core/kiln_ai/datamodel/prompt_optimization_job.py, libs/core/kiln_ai/datamodel/task.py, libs/core/kiln_ai/datamodel/eval.py
New PromptOptimizationJob model with job orchestration fields; added starred field to TaskRunConfig; added prompt_optimization_jobs accessor to Task; added migrate_train_set_filter_id validator to Eval for legacy migration; removed SHORT enum member from PromptGenerators.
Core CLI/Packaging
libs/core/kiln_ai/cli/commands/package_project.py
Added non-CLI validation and build functions (validate_tasks_noncli, validate_and_build_prompts_noncli); added PackageForTrainingConfig and training-package export functions (export_evals, export_documents, export_task_runs, package_project_for_training).
Prompt Builder Removal
libs/core/kiln_ai/adapters/prompt_builders.py, libs/server/kiln_server/prompt_api.py
Removed ShortPromptBuilder class; removed SHORT enum handling; updated prompt generator catalog (removed short generator, renamed few/multi-shot chain-of-thought variants); added generator_id and created_at fields to prompt creation/response models.
Web UI — Optimize Routes
app/web_ui/src/routes/(app)/optimize/[project_id]/[task_id]/+page.svelte, .../run_config/..., .../+page.ts
New optimize page (491 lines) displaying run configurations with sorting, starring, cloning, and comparison; new run config detail and creation pages; optimizer carousel with feature metrics.
Web UI — Prompt Optimization Routes
app/web_ui/src/routes/(app)/prompt_optimization/[project_id]/[task_id]/...
New job listing page, job detail page with polling, and job creation page (1181 lines) with run config/eval selection and validation; Copilot auth page.
Web UI — Prompts Routes Updates
app/web_ui/src/routes/(app)/prompts/[project_id]/[task_id]/...
Refactored create flow to support generator_id and chain-of-thought modes; added generator browsing page (163 lines); added base prompt edit page; removed generator_details page; updated saved prompt view with Type field and optimize breadcrumbs.
Web UI — Layout & Navigation
app/web_ui/src/routes/(app)/+layout.svelte, app/web_ui/src/routes/(app)/docs/..., .../fine_tune/..., .../models/...
Added Optimize section to navigation with nested submenu; added breadcrumbs prop to multiple pages; added task context to docs page; expanded models page with pricing, featured rank, and featured capability filtering (605 lines).
Web UI — UI Components
app/web_ui/src/lib/ui/banner.svelte, .../float.svelte, .../icons/optimize_icon.svelte, .../icons/star_icon.svelte, .../carousel_section.svelte, .../feature_carousel.svelte
New banner, float (Floating UI wrapper), and icon components; updated carousel_section with disabled-state dialog and recommended badges; updated feature_carousel with grid layout and metrics visualization; new intro markdown support.
Web UI — Kiln Copilot Components
app/web_ui/src/lib/ui/kiln_copilot/copilot_auth_page.svelte, .../copilot_required_card.svelte, .../entitlement_required_card.svelte
New Copilot auth page, Copilot requirement card, and entitlement requirement card components for feature gating.
Web UI — Run Config Components
app/web_ui/src/lib/ui/run_config_component/...
Added clone mode to create_new_run_config_dialog; added name field to run_config_component; removed run_config_details_dialog; refactored run_config_summary to navigate to detail page instead of opening dialog.
Web UI — Utilities & Types
app/web_ui/src/lib/utils/entitlement_utils.ts, .../copilot_utils.ts, .../name_generator.ts, .../link_builder.ts, app/web_ui/src/lib/types.ts, .../stores/run_configs_store.ts
New entitlement checking and memorable name generation utilities; updated link_builder to remove non-ID generator_details path; added PromptOptimizationJob and JobStatus types; updated run_configs_store.save_new_task_run_config to include name parameter.
Web UI — API Schema
app/web_ui/src/lib/api_schema.d.ts
Generated schema updates including new /api/check_entitlements endpoint, /api/prompt_optimization_jobs routes, Eval/RunConfig PATCH operations, and new response/request types (PublicPromptOptimizationJobStatusResponse, CheckEvalResponse, UpdateEvalRequest, etc.).
Tests
app/desktop/studio_server/test_eval_api.py, .../test_settings_api.py, .../utils/test_response_utils.py, libs/core/kiln_ai/datamodel/test_prompt_optimization_job.py, libs/core/kiln_ai/datamodel/test_eval_model.py, libs/core/kiln_ai/cli/commands/test_package_project.py
Comprehensive tests for new eval/run config PATCH endpoints, entitlements checking, response unwrapping utilities, PromptOptimizationJob persistence and relationships, Eval train_set_filter_id migration, and training-package export flows.

Sequence Diagram

sequenceDiagram
    participant Client as Client (Web UI)
    participant Server as Server
    participant KilnAI as Kiln AI API
    participant DB as Database

    rect rgba(100, 150, 200, 0.5)
    Note over Client,DB: Check Entitlements Flow
    Client->>Server: GET /api/check_entitlements?feature_codes=prompt-optimization
    Server->>KilnAI: GET /v1/check_entitlements (with API key)
    KilnAI->>KilnAI: Validate feature codes
    KilnAI-->>Server: CheckEntitlementsResponse {prompt-optimization: true/false}
    Server-->>Client: {prompt-optimization: true/false}
    end

    rect rgba(150, 200, 100, 0.5)
    Note over Client,DB: Prompt Optimization Job Flow
    Client->>Server: POST /api/projects/{id}/tasks/{id}/prompt_optimization_jobs/start
    Server->>Server: Validate run config & evals
    Server->>KilnAI: POST /v1/jobs/prompt_optimization_job/start (with ZIP)
    KilnAI->>KilnAI: Queue job
    KilnAI-->>Server: {job_id: "...", status: "pending"}
    Server->>DB: Save PromptOptimizationJob
    Server-->>Client: {job_id, status}
    
    Client->>Server: GET /api/prompt_optimization_jobs/{job_id}/status (polling)
    Server->>KilnAI: GET /v1/jobs/prompt_optimization_job/{job_id}/status
    KilnAI-->>Server: {status: "running"/"succeeded"}
    Server-->>Client: {status: "running"/"succeeded"}
    
    alt Job Succeeded
        Server->>KilnAI: GET /v1/jobs/prompt_optimization_job/{job_id}/result
        KilnAI-->>Server: {optimized_prompt: "..."}
        Server->>Server: Create Prompt from optimized text
        Server->>Server: Create RunConfig from optimized Prompt
        Server->>DB: Save Prompt & RunConfig
        Server->>DB: Update PromptOptimizationJob with artifact IDs
    end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

codex, feature, server-api, web-ui, data-model

Poem

🐰 Optimizing Prompts with a Hop and Skip

A rabbit built a flow so grand,
Where prompts dance and jobs expand,
Entitlements checked with a feature key,
Run configs starred for all to see! ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The pull request description is entirely empty, missing all required template sections including 'What does this PR do?', related issues, CLA confirmation, and checklists. Add a comprehensive description following the template: explain the specific UI polish changes, link any related issues, confirm CLA agreement, and check the required boxes for testing and new tests.
Docstring Coverage ⚠️ Warning Docstring coverage is 37.93% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'copilot connect UI polish' clearly and specifically describes the main focus of the changeset, which involves UI improvements related to Copilot connection features.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch sfierro/copilot-connect-polish

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

Copy link
Copy Markdown

📊 Coverage Report

Overall Coverage: 91%

Diff: origin/main...HEAD

  • app/desktop/desktop_server.py (100%)
  • app/desktop/studio_server/copilot_api.py (66.7%): Missing lines 209,235
  • app/desktop/studio_server/eval_api.py (97.4%): Missing lines 494
  • app/desktop/studio_server/prompt_optimization_job_api.py (92.3%): Missing lines 150,153,158,222,233-234,237-238,361,372,428,597-598,633-634,650,670-671,674,689
  • app/desktop/studio_server/settings_api.py (100%)
  • app/desktop/studio_server/utils/copilot_utils.py (50.0%): Missing lines 94
  • app/desktop/studio_server/utils/response_utils.py (100%)
  • libs/core/kiln_ai/adapters/prompt_builders.py (100%)
  • libs/core/kiln_ai/cli/commands/package_project.py (92.9%): Missing lines 445,817,829,848,850,868,870,889
  • libs/core/kiln_ai/datamodel/init.py (100%)
  • libs/core/kiln_ai/datamodel/eval.py (90.9%): Missing lines 443
  • libs/core/kiln_ai/datamodel/prompt_optimization_job.py (94.7%): Missing lines 8
  • libs/core/kiln_ai/datamodel/task.py (80.0%): Missing lines 192
  • libs/server/kiln_server/prompt_api.py (100%)

Summary

  • Total: 501 lines
  • Missing: 35 lines
  • Coverage: 93%

Line-by-line

View line-by-line diff coverage

app/desktop/studio_server/copilot_api.py

Lines 205-213

  205                 client=client,
  206                 body=questioner_input,
  207             )
  208         )
! 209         result = unwrap_response(
  210             detailed_result,
  211             none_detail="Failed to generate clarifying questions for spec. Please try again.",
  212         )

Lines 231-239

  231         detailed_result = await refine_spec_with_answers_v1_copilot_refine_spec_with_answers_post.asyncio_detailed(
  232             client=client,
  233             body=submit_input,
  234         )
! 235         result = unwrap_response(
  236             detailed_result,
  237             none_detail="Failed to refine spec with question answers. Please try again.",
  238         )

app/desktop/studio_server/eval_api.py

Lines 490-498

  490                 status_code=400,
  491                 detail="Cannot update this run config.",
  492             )
  493         if request.name is not None:
! 494             run_config.name = request.name
  495         if request.starred is not None:
  496             run_config.starred = request.starred
  497         if request.prompt_name is not None:
  498             if run_config.prompt is None:

app/desktop/studio_server/prompt_optimization_job_api.py

Lines 146-162

  146     Raises exceptions on failure.
  147     """
  148     parent_project = task.parent_project()
  149     if parent_project is None:
! 150         raise HTTPException(status_code=500, detail="Task has no parent project")
  151 
  152     if not parent_project.id or not task.id:
! 153         raise HTTPException(
  154             status_code=500, detail="Task has no parent project or task"
  155         )
  156 
  157     if not prompt.id:
! 158         raise HTTPException(status_code=500, detail="Prompt has no ID")
  159 
  160     # get the original target run config that we optimized for in the job
  161     target_run_config = task_run_config_from_id(
  162         parent_project.id, task.id, prompt_optimization_job.target_run_config_id

Lines 218-226

  218         or not parent_project.id
  219         or not task.id
  220         or not prompt_optimization_job.id
  221     ):
! 222         raise ValueError("Cannot reload Prompt Optimization job: missing required IDs")
  223 
  224     # reload the job in case artifacts were created by another request while waiting for the lock
  225     reloaded_job = prompt_optimization_job_from_id(
  226         parent_project.id,

Lines 229-242

  229     )
  230 
  231     # check if artifacts already exist
  232     if reloaded_job.created_prompt_id:
! 233         prompt_optimization_job.created_prompt_id = reloaded_job.created_prompt_id
! 234         prompt_optimization_job.created_run_config_id = (
  235             reloaded_job.created_run_config_id
  236         )
! 237         prompt_optimization_job.optimized_prompt = reloaded_job.optimized_prompt
! 238         return
  239 
  240     detailed_response = await get_prompt_optimization_job_result_v1_jobs_prompt_optimization_job_job_id_result_get.asyncio_detailed(
  241         job_id=prompt_optimization_job.job_id,
  242         client=server_client,

Lines 357-365

  357                 return CheckRunConfigResponse(is_supported=False)
  358 
  359             server_client = get_authenticated_client(_get_api_key())
  360             if not isinstance(server_client, AuthenticatedClient):
! 361                 raise HTTPException(
  362                     status_code=500, detail="Server client not authenticated"
  363                 )
  364 
  365             detailed_response = await check_prompt_optimization_model_supported_v1_jobs_prompt_optimization_job_check_model_supported_get.asyncio_detailed(

Lines 368-376

  368                 model_provider_name=model_provider.value,
  369             )
  370             response = unwrap_response(detailed_response)
  371 
! 372             return CheckRunConfigResponse(is_supported=response.is_model_supported)
  373 
  374         except HTTPException:
  375             raise
  376         except Exception as e:

Lines 424-432

  424                 )
  425 
  426             server_client = get_authenticated_client(_get_api_key())
  427             if not isinstance(server_client, AuthenticatedClient):
! 428                 raise HTTPException(
  429                     status_code=500, detail="Server client not authenticated"
  430                 )
  431 
  432             # EvalConfig.model_provider is already a string, no need for .value

Lines 593-602

  593                                 for job in batch
  594                             ],
  595                             return_exceptions=True,
  596                         )
! 597             except Exception as e:
! 598                 logger.error(
  599                     f"Error updating Prompt Optimization job statuses: {e}",
  600                     exc_info=True,
  601                 )

Lines 629-638

  629                     await update_prompt_optimization_job_and_create_artifacts(
  630                         prompt_optimization_job, server_client
  631                     )
  632                 )
! 633         except Exception as e:
! 634             logger.error(
  635                 f"Error updating Prompt Optimization job status: {e}", exc_info=True
  636             )
  637 
  638         return prompt_optimization_job

Lines 646-654

  646         """
  647         try:
  648             server_client = get_authenticated_client(_get_api_key())
  649             if not isinstance(server_client, AuthenticatedClient):
! 650                 raise HTTPException(
  651                     status_code=500, detail="Server client not authenticated"
  652                 )
  653 
  654             detailed_response = await get_job_status_v1_jobs_job_type_job_id_status_get.asyncio_detailed(

Lines 666-678

  666             )
  667 
  668         except HTTPException:
  669             raise
! 670         except Exception as e:
! 671             logger.error(
  672                 f"Error getting prompt optimization job status: {e}", exc_info=True
  673             )
! 674             raise HTTPException(
  675                 status_code=500,
  676                 detail=f"Failed to get Prompt Optimization job status: {str(e)}",
  677             )

Lines 685-693

  685         """
  686         try:
  687             server_client = get_authenticated_client(_get_api_key())
  688             if not isinstance(server_client, AuthenticatedClient):
! 689                 raise HTTPException(
  690                     status_code=500, detail="Server client not authenticated"
  691                 )
  692 
  693             detailed_response = await get_prompt_optimization_job_result_v1_jobs_prompt_optimization_job_job_id_result_get.asyncio_detailed(

app/desktop/studio_server/utils/copilot_utils.py

Lines 90-98

  90             client=client,
  91             body=generate_input,
  92         )
  93     )
! 94     result = unwrap_response(
  95         detailed_result,
  96         none_detail="Failed to generate synthetic data for spec. Please try again.",
  97     )

libs/core/kiln_ai/cli/commands/package_project.py

Lines 441-449

  441     task_prompts: dict[str, Prompt] = {}
  442 
  443     for task in tasks:
  444         if task.id is None:
! 445             raise ValueError(f"Task '{task.name}' ID is not set")
  446 
  447         run_config = run_configs[task.id]
  448         prompt_id = run_config.run_config_properties.prompt_id

Lines 813-821

  813     Returns:
  814         List of eval IDs that were actually exported (intersection of eval_ids and task's evals)
  815     """
  816     if exported_task.path is None:
! 817         raise ValueError("Exported task path is not set")
  818 
  819     exported_eval_ids: list[str] = []
  820     evals = task.evals(readonly=True)
  821     eval_ids_set = set(eval_ids)

Lines 825-833

  825     for eval_obj in evals:
  826         if eval_obj.id not in eval_ids_set:
  827             continue
  828         if eval_obj.path is None:
! 829             raise ValueError(f"Eval '{eval_obj.name}' path is not set")
  830 
  831         eval_dir = eval_obj.path.parent
  832         dest_dir = exported_task.path.parent / "evals" / eval_dir.name

Lines 844-854

  844         project: The source project
  845         exported_project: The exported project to copy documents into
  846     """
  847     if project.path is None:
! 848         raise ValueError("Project path is not set")
  849     if exported_project.path is None:
! 850         raise ValueError("Exported project path is not set")
  851 
  852     source_docs_dir = project.path.parent / "documents"
  853     if not source_docs_dir.exists() or not source_docs_dir.is_dir():
  854         return

Lines 864-874

  864         task: The source task containing the runs
  865         exported_task: The exported task to copy runs into
  866     """
  867     if task.path is None:
! 868         raise ValueError(f"Task '{task.name}' path is not set")
  869     if exported_task.path is None:
! 870         raise ValueError("Exported task path is not set")
  871 
  872     source_runs_dir = task.path.parent / "runs"
  873     if not source_runs_dir.exists() or not source_runs_dir.is_dir():
  874         return

Lines 885-893

  885     """
  886     run_configs = task.run_configs()
  887     run_config = next((rc for rc in run_configs if rc.id == run_config_id), None)
  888     if not run_config:
! 889         raise ValueError(
  890             f"Run config '{run_config_id}' not found for task '{task.name}' (ID: {task.id})"
  891         )
  892     return run_config

libs/core/kiln_ai/datamodel/eval.py

Lines 439-447

  439         Generates a tag-based filter ID from the eval name following the convention
  440         used by spec-based evals (e.g., "train_{name_slug}").
  441         """
  442         if self.id is None:
! 443             return self
  444 
  445         if not self._loaded_from_file:
  446             return self

libs/core/kiln_ai/datamodel/prompt_optimization_job.py

Lines 4-12

   4 
   5 from kiln_ai.datamodel.basemodel import FilenameString, KilnParentedModel
   6 
   7 if TYPE_CHECKING:
!  8     from kiln_ai.datamodel.task import Task
   9 
  10 
  11 class PromptOptimizationJob(KilnParentedModel):
  12     """

libs/core/kiln_ai/datamodel/task.py

Lines 188-196

  188 
  189     def prompt_optimization_jobs(
  190         self, readonly: bool = False
  191     ) -> list[PromptOptimizationJob]:
! 192         return super().prompt_optimization_jobs(readonly=readonly)  # type: ignore
  193 
  194     # Workaround to return typed parent without importing Task
  195     def parent_project(self) -> Union["Project", None]:
  196         if self.parent is None or self.parent.__class__.__name__ != "Project":


@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @sfierro, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a significant new feature: automatic prompt optimization. Users can now leverage Kiln's Copilot capabilities to refine their prompts based on evaluation results and training data, aiming to maximize prompt quality. Alongside this, the PR includes extensive UI/UX polish, particularly in the newly structured 'Optimize' section, the models browsing experience, and prompt creation workflows. Backend changes involve new API endpoints for managing optimization jobs, updated data models, and a refactored approach to API response error handling, ensuring a more robust and user-friendly experience.

Highlights

  • New Prompt Optimization Feature: Introduced a comprehensive 'Prompt Optimization' feature, allowing users to automatically refine prompts using evaluations and training data. This includes new API endpoints and UI pages for managing optimization jobs.
  • Enhanced UI/UX: Significant UI/UX improvements were made across the application, particularly in the newly structured 'Optimize' section, the models browsing experience, and prompt management workflows.
  • API Client and Error Handling Refactor: Refactored the API client to support new features like entitlement checks and standardized API response error handling with a new response_utils module for improved robustness.
  • Updated Data Models: New data models were added, such as PromptOptimizationJob, and existing models were updated to support new functionalities, including starred run configurations and train_dataset_size in evaluations.
  • Deprecation of Task Requirements: The 'requirements' field in tasks has been deprecated in favor of the more powerful Specs & Evals and Prompts features, with UI updates reflecting this change.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • app/desktop/desktop_server.py
    • Imported and connected the new connect_prompt_optimization_job_api.
  • app/desktop/studio_server/api_client/kiln_ai_server_client/api/auth/check_entitlements_v1_check_entitlements_get.py
    • Added a new API client for checking user entitlements.
  • app/desktop/studio_server/api_client/kiln_ai_server_client/api/auth/secure_ping_v1_ping_get.py
    • Added a blank line for formatting.
  • app/desktop/studio_server/api_client/kiln_ai_server_client/api/auth/verify_api_key_v1_verify_api_key_get.py
    • Added a blank line for formatting.
  • app/desktop/studio_server/api_client/kiln_ai_server_client/api/health/health_health_get.py
    • Added a blank line for formatting.
  • app/desktop/studio_server/api_client/kiln_ai_server_client/api/health/ping_ping_get.py
    • Added a blank line for formatting.
  • app/desktop/studio_server/api_client/kiln_ai_server_client/api/jobs/check_prompt_optimization_model_supported_v1_jobs_prompt_optimization_job_check_model_supported_get.py
    • Renamed from check_model_supported_v1_jobs_gepa_job_check_model_supported_get.py.
    • Updated URL path and docstrings to reflect 'prompt_optimization_job'.
    • Added a blank line for formatting.
  • app/desktop/studio_server/api_client/kiln_ai_server_client/api/jobs/get_job_status_v1_jobs_job_type_job_id_status_get.py
    • Added a blank line for formatting.
  • app/desktop/studio_server/api_client/kiln_ai_server_client/api/jobs/get_prompt_optimization_job_result_v1_jobs_prompt_optimization_job_job_id_result_get.py
    • Renamed from get_gepa_job_result_v1_jobs_gepa_job_job_id_result_get.py.
    • Updated imports and types to use PromptOptimizationJobResultResponse instead of GEPAJobResultResponse.
    • Updated URL path and docstrings to reflect 'prompt_optimization_job'.
    • Added a blank line for formatting.
  • app/desktop/studio_server/api_client/kiln_ai_server_client/api/jobs/get_sample_job_result_v1_jobs_sample_job_job_id_result_get.py
    • Added a blank line for formatting.
  • app/desktop/studio_server/api_client/kiln_ai_server_client/api/jobs/start_prompt_optimization_job_v1_jobs_prompt_optimization_job_start_post.py
    • Renamed from start_gepa_job_v1_jobs_gepa_job_start_post.py.
    • Updated imports and types to use BodyStartPromptOptimizationJobV1JobsPromptOptimizationJobStartPost.
    • Updated URL path and docstrings to reflect 'prompt_optimization_job'.
    • Modified request body to remove token_budget and add eval_ids.
  • app/desktop/studio_server/api_client/kiln_ai_server_client/models/init.py
    • Updated imports and __all__ to reflect renaming of GEPA-related models to PromptOptimizationJob and added CheckEntitlementsV1CheckEntitlementsGetResponseCheckEntitlementsV1CheckEntitlementsGet.
    • Removed BodyStartGepaJobV1JobsGepaJobStartPostTokenBudget, GEPAJobOutput, GEPAJobResultResponse.
    • Added PromptOptimizationJobOutput, PromptOptimizationJobResultResponse.
  • app/desktop/studio_server/api_client/kiln_ai_server_client/models/body_start_gepa_job_v1_jobs_gepa_job_start_post_token_budget.py
    • Removed this file.
  • app/desktop/studio_server/api_client/kiln_ai_server_client/models/body_start_prompt_optimization_job_v1_jobs_prompt_optimization_job_start_post.py
    • Renamed from body_start_gepa_job_v1_jobs_gepa_job_start_post.py.
    • Removed token_budget field and added eval_ids field.
  • app/desktop/studio_server/api_client/kiln_ai_server_client/models/check_entitlements_v1_check_entitlements_get_response_check_entitlements_v1_check_entitlements_get.py
    • Added a new model for the entitlement check API response.
  • app/desktop/studio_server/api_client/kiln_ai_server_client/models/generate_batch_output_data_by_topic.py
    • Added a blank line for formatting.
  • app/desktop/studio_server/api_client/kiln_ai_server_client/models/health_health_get_response_health_health_get.py
    • Added a blank line for formatting.
  • app/desktop/studio_server/api_client/kiln_ai_server_client/models/prompt_optimization_job_output.py
    • Renamed from gepa_job_output.py.
    • Updated class name and docstring.
  • app/desktop/studio_server/api_client/kiln_ai_server_client/models/prompt_optimization_job_result_response.py
    • Renamed from gepa_job_result_response.py.
    • Updated class name, imports, and docstring.
  • app/desktop/studio_server/api_client/kiln_ai_server_client/models/spec_spec_field_current_values.py
    • Added a blank line for formatting.
  • app/desktop/studio_server/api_client/kiln_ai_server_client/models/spec_spec_fields.py
    • Added a blank line for formatting.
  • app/desktop/studio_server/api_client/kiln_ai_server_client/models/specification_input_spec_field_current_values.py
    • Added a blank line for formatting.
  • app/desktop/studio_server/api_client/kiln_ai_server_client/models/specification_input_spec_fields.py
    • Added a blank line for formatting.
  • app/desktop/studio_server/copilot_api.py
    • Removed HTTPValidationError import.
    • Replaced check_response_error calls with unwrap_response.
  • app/desktop/studio_server/eval_api.py
    • Added UpdateRunConfigRequest and UpdateEvalRequest models.
    • Added train_dataset_size to EvalProgress.
    • Implemented new PATCH endpoints for /eval/{eval_id} and /run_config/{run_config_id} to update eval and run config properties.
  • app/desktop/studio_server/prompt_optimization_job_api.py
    • Added a new API module for managing prompt optimization jobs.
    • Implemented endpoints for checking run config/eval validity, starting a job, listing jobs, getting a specific job, and retrieving job status/results.
    • Included logic for creating new prompts and run configs upon successful optimization.
  • app/desktop/studio_server/settings_api.py
    • Added a new GET endpoint /api/check_entitlements to check user entitlements.
  • app/desktop/studio_server/test_eval_api.py
    • Added tests for TaskRunConfig.starred field.
    • Added tests for update_run_config endpoint, including name, starred status, and prompt name updates.
    • Added tests for update_eval endpoint, covering name, description, and train_set_filter_id updates.
  • app/desktop/studio_server/test_settings_api.py
    • Added tests for the new check_entitlements API endpoint, covering various scenarios like no API key, single/multiple features, and API errors.
  • app/desktop/studio_server/utils/copilot_utils.py
    • Removed json import and check_response_error function.
    • Updated to use unwrap_response from response_utils.
  • app/desktop/studio_server/utils/response_utils.py
    • Added a new utility module for standardized API response handling, including check_response_error, unwrap_response_allow_none, and unwrap_response.
  • app/desktop/studio_server/utils/test_response_utils.py
    • Added tests for the new response_utils module, covering various HTTP status codes and JSON parsing scenarios.
  • app/web_ui/package-lock.json
    • Reordered dependencies in package-lock.json.
  • app/web_ui/src/lib/api_schema.d.ts
    • Updated API schema to include new endpoints and data models for prompt optimization, eval updates, and run config updates.
  • app/web_ui/src/lib/stores/run_configs_store.ts
    • Modified save_new_task_run_config to accept a name parameter.
  • app/web_ui/src/lib/types.ts
    • Added new TypeScript types for PublicPromptOptimizationJobStatusResponse, PromptOptimizationJob, and JobStatus.
  • app/web_ui/src/lib/ui/banner.svelte
    • Added a new Svelte component for displaying promotional banners.
  • app/web_ui/src/lib/ui/carousel_section.svelte
    • Enhanced the carousel section component with min_width, min_height props, and logic for handling disabled items with reasons and documentation links.
    • Added support for a 'recommended' badge.
  • app/web_ui/src/lib/ui/completed.svelte
    • Refactored the Completed component to utilize the Intro component for a more consistent UI.
  • app/web_ui/src/lib/ui/edit_dialog.svelte
    • Added a close function to the edit dialog component.
  • app/web_ui/src/lib/ui/feature_carousel.svelte
    • Improved the layout and styling of the feature carousel, including dynamic column sizing based on content and the ability to display metrics.
  • app/web_ui/src/lib/ui/feature_carousel_types.ts
    • Extended CarouselFeature type to include optional metrics and made subtitle optional.
  • app/web_ui/src/lib/ui/float.svelte
    • Added a new Svelte component for creating floating UI elements, leveraging @floating-ui/dom.
  • app/web_ui/src/lib/ui/icons/optimize_icon.svelte
    • Added a new SVG icon for optimization.
  • app/web_ui/src/lib/ui/icons/star_icon.svelte
    • Added a new SVG icon for starring items.
  • app/web_ui/src/lib/ui/intro.svelte
    • Introduced a description_markdown prop and conditional rendering for descriptions in the Intro component.
  • app/web_ui/src/lib/ui/kiln_copilot/copilot_auth_page.svelte
    • Added a new Svelte component to streamline the Kiln Copilot authentication flow.
  • app/web_ui/src/lib/ui/kiln_copilot/copilot_required_card.svelte
    • Added a new Svelte component to display a card indicating when Kiln Copilot is required for a feature.
  • app/web_ui/src/lib/ui/kiln_copilot/entitlement_required_card.svelte
    • Added a new Svelte component to inform users when a specific entitlement is required for a feature.
  • app/web_ui/src/lib/ui/kiln_section_types.ts
    • Defined a new PromptGeneratorItem interface and updated CarouselSectionItem to include it.
  • app/web_ui/src/lib/ui/markdown_block.svelte
    • Enhanced the markdown block component to correctly handle newlines and empty lines in markdown text.
  • app/web_ui/src/lib/ui/property_list.svelte
    • Adjusted styling for badges and property values to allow wrapping.
  • app/web_ui/src/lib/ui/run_config_component/create_new_run_config_dialog.svelte
    • Extended the dialog component with mode, source_run_config, hide_tools_selector, model, run_config_name props, and a showClone function.
  • app/web_ui/src/lib/ui/run_config_component/run_config_component.svelte
    • Added run_config_name, show_name_field, and model props.
    • Modified save_new_run_config to pass the run_config_name.
  • app/web_ui/src/lib/ui/run_config_component/run_config_details_dialog.svelte
    • Removed this Svelte component.
  • app/web_ui/src/lib/ui/run_config_component/run_config_summary.svelte
    • Replaced the RunConfigDetailsDialog with direct navigation to a new run config detail page.
    • Added a click handler for navigation.
  • app/web_ui/src/lib/ui/settings_header.svelte
    • Added a subtitle prop to the settings header component.
  • app/web_ui/src/lib/utils/copilot_utils.ts
    • Added a new utility module for checking Kiln Copilot availability.
  • app/web_ui/src/lib/utils/entitlement_utils.ts
    • Added a new utility module for checking user entitlements, specifically for prompt optimization access.
  • app/web_ui/src/lib/utils/form_list.svelte
    • Added a hide_add_button prop to the form list component.
  • app/web_ui/src/lib/utils/link_builder.test.ts
    • Simplified prompt_link test cases by removing generator_details paths.
  • app/web_ui/src/lib/utils/link_builder.ts
    • Simplified prompt_link logic by removing the generator_details path.
  • app/web_ui/src/lib/utils/name_generator.ts
    • Added a new utility module for generating memorable names.
  • app/web_ui/src/routes/(app)/+layout.svelte
    • Integrated a new 'Optimize' section into the sidebar navigation.
    • Nested 'Prompts', 'Models', 'Fine Tune', and 'Docs & Search' under the 'Optimize' section in the sidebar.
  • app/web_ui/src/routes/(app)/dataset/[project_id]/[task_id]/add_data/+page.svelte
    • Modified the handling of the reason parameter to exclude 'generic' values.
  • app/web_ui/src/routes/(app)/docs/[project_id]/+page.svelte
    • Added breadcrumbs that link back to the 'Optimize' section.
  • app/web_ui/src/routes/(app)/fine_tune/[project_id]/[task_id]/+page.svelte
    • Added breadcrumbs that link back to the 'Optimize' section.
  • app/web_ui/src/routes/(app)/generate/[project_id]/[task_id]/+page.svelte
    • Corrected the type handling for the reason_param variable.
  • app/web_ui/src/routes/(app)/models/+page.svelte
    • Introduced reactive project_id and task_id variables.
    • Added pricingLoading state and a loadPricing function.
    • Implemented a 'featured' capability filter.
    • Updated the model card UI to display pricing information and a 'Try' button.
    • Added connect_provider_dialog and create_run_config_dialog for improved model interaction.
    • Added breadcrumbs that link back to the 'Optimize' section.
  • app/web_ui/src/routes/(app)/models/price.ts
    • Added a new utility module for fetching and managing model pricing data.
  • app/web_ui/src/routes/(app)/optimize/[project_id]/[task_id]/+page.svelte
    • Added a new page for the 'Optimize' section, featuring optimization strategies and a table of run configurations with sorting, selection for comparison, cloning, and default setting functionalities.
  • app/web_ui/src/routes/(app)/optimize/[project_id]/[task_id]/+page.ts
    • Set prerender to false for this page.
  • app/web_ui/src/routes/(app)/optimize/[project_id]/[task_id]/optimizers.ts
    • Defined optimization strategies and their associated properties.
  • app/web_ui/src/routes/(app)/optimize/[project_id]/[task_id]/run_config/[run_config_id]/+page.svelte
    • Added a new page for displaying and editing individual run configuration details.
  • app/web_ui/src/routes/(app)/optimize/[project_id]/[task_id]/run_config/[run_config_id]/+page.ts
    • Set prerender to false for this page.
  • app/web_ui/src/routes/(app)/optimize/[project_id]/[task_id]/run_config/create/+page.svelte
    • Added a new page for creating a run configuration, with options to pre-fill model or prompt information.
  • app/web_ui/src/routes/(app)/optimize/[project_id]/[task_id]/run_config/create/+page.ts
    • Set prerender to false for this page.
  • app/web_ui/src/routes/(app)/prompt_optimization/[project_id]/[task_id]/+page.svelte
    • Added a new page to list prompt optimization jobs, displaying their status, creation date, and links to detailed job views.
    • Includes checks for Kiln Copilot connectivity and user entitlements.
  • app/web_ui/src/routes/(app)/prompt_optimization/[project_id]/[task_id]/+page.ts
    • Set prerender to false for this page.
  • app/web_ui/src/routes/(app)/prompt_optimization/[project_id]/[task_id]/create_prompt_optimization_job/+page.svelte
    • Added a new page for initiating a prompt optimization job, guiding users through selecting a target run configuration and relevant evaluations, complete with validation and error handling.
  • app/web_ui/src/routes/(app)/prompt_optimization/[project_id]/[task_id]/create_prompt_optimization_job/+page.ts
    • Set prerender to false for this page.
  • app/web_ui/src/routes/(app)/prompt_optimization/[project_id]/[task_id]/prompt_optimization_job/[job_id]/+page.svelte
    • Added a new page to display the detailed status and results of a specific prompt optimization job, including the optimized prompt, job properties, and related run configurations/evaluations.
    • Implemented polling for real-time status updates.
  • app/web_ui/src/routes/(app)/prompt_optimization/[project_id]/[task_id]/prompt_optimization_job/[job_id]/+page.ts
    • Set prerender to false for this page.
  • app/web_ui/src/routes/(app)/prompt_optimization/copilot_auth/+page.svelte
    • Added a new page for handling Kiln Copilot authentication specifically for prompt optimization features.
  • app/web_ui/src/routes/(app)/prompt_optimization/copilot_auth/+page.ts
    • Set ssr to false for this page.
  • app/web_ui/src/routes/(app)/prompts/[project_id]/[task_id]/+page.svelte
    • Updated the main prompts page to feature a banner for automatic optimization and a dedicated section for editing the base task prompt.
    • Replaced the prompt generators section with a table for saved prompts, offering sorting and additional actions.
  • app/web_ui/src/routes/(app)/prompts/[project_id]/[task_id]/create/+page.svelte
    • Modified the prompt creation page to support generating prompts from predefined generators, including pre-filling prompt content and managing generator-specific logic.
  • app/web_ui/src/routes/(app)/prompts/[project_id]/[task_id]/edit_base_prompt/+page.svelte
    • Added a new page for editing the task's base prompt and thinking instructions.
  • app/web_ui/src/routes/(app)/prompts/[project_id]/[task_id]/edit_base_prompt/+page.ts
    • Set prerender to false for this page.
  • app/web_ui/src/routes/(app)/prompts/[project_id]/[task_id]/generator_details/[generator_id]/+page.svelte
    • Removed this page.
  • app/web_ui/src/routes/(app)/prompts/[project_id]/[task_id]/prompt_generators/+page.svelte
    • Added a new page to list and categorize available prompt generators, with dynamic disabled states based on data availability.
  • app/web_ui/src/routes/(app)/prompts/[project_id]/[task_id]/prompt_generators/+page.ts
    • Set prerender to false for this page.
  • app/web_ui/src/routes/(app)/prompts/[project_id]/[task_id]/prompt_generators/prompt_generators.ts
    • Defined prompt generator categories and templates, introducing a new 'Kiln Optimized' generator.
  • app/web_ui/src/routes/(app)/prompts/[project_id]/[task_id]/saved/[prompt_id]/+page.svelte
    • Updated the saved prompt detail page to reflect changes in prompt properties, such as the removal of 'chain of thought' and 'source generator' fields, and the addition of a 'type' field.
  • app/web_ui/src/routes/(app)/run/+page.svelte
    • Modified the run page to support pre-selecting a model via a URL parameter.
  • app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/+page.svelte
    • Replaced a custom banner implementation with the new Banner component.
  • app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/[spec_id]/+page.svelte
    • Added a max_length attribute to the spec name edit field.
  • app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/[spec_id]/[eval_id]/+page.svelte
    • Added display for train_dataset_size in eval properties.
    • Added max_length attribute to the eval name edit field.
  • app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/compare/+page.svelte
    • Added a fromOptimize parameter to the breadcrumbs for contextual navigation.
  • app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/spec_builder/+page.svelte
    • Updated imports for checkKilnCopilotAvailable to use the new copilot_utils module.
  • app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/spec_utils.ts
    • Removed the checkKilnCopilotAvailable function, as it has been moved to copilot_utils.ts.
  • app/web_ui/src/routes/(app)/specs/copilot_auth/+page.svelte
    • Refactored this page to utilize the new CopilotAuthPage component.
  • app/web_ui/src/routes/(fullscreen)/setup/(setup)/create_task/edit_task.svelte
    • Integrated the new Collapse component for advanced options.
    • Updated the description for the instruction field.
    • Deprecated the requirements field and hid its add button.
  • libs/core/kiln_ai/adapters/prompt_builders.py
    • Added an extra newline before 'Thinking Instructions' for improved formatting.
    • Removed the ShortPromptBuilder class.
  • libs/core/kiln_ai/adapters/test_prompt_builders.py
    • Removed test cases specifically for ShortPromptBuilder.
  • libs/core/kiln_ai/cli/commands/package_project.py
    • Introduced the PackageForTrainingConfig dataclass.
    • Added validate_tasks_noncli and validate_and_build_prompts_noncli functions for non-CLI task and prompt validation.
    • Implemented export_evals, export_documents, and export_task_runs functions for selective project packaging.
    • Added package_project_for_training function for non-CLI use cases.
  • libs/core/kiln_ai/cli/commands/test_package_project.py
    • Added comprehensive test cases for validate_tasks_noncli and validate_and_build_prompts_noncli.
    • Included extensive tests for package_project_for_training and its various configuration options, covering evals, documents, and task runs.
  • libs/core/kiln_ai/datamodel/init.py
    • Imported and exported the new PromptOptimizationJob data model.
  • libs/core/kiln_ai/datamodel/datamodel_enums.py
    • Clarified the description for the Priority enumeration.
  • libs/core/kiln_ai/datamodel/eval.py
    • Implemented a migrate_train_set_filter_id model validator to automatically generate train_set_filter_id for legacy evals upon loading.
  • libs/core/kiln_ai/datamodel/prompt_id.py
    • Removed the SHORT prompt generator.
  • libs/core/kiln_ai/datamodel/prompt_optimization_job.py
    • Added a new data model for PromptOptimizationJob.
  • libs/core/kiln_ai/datamodel/task.py
    • Imported PromptOptimizationJob.
    • Added a starred field to TaskRunConfig.
    • Established a prompt_optimization_jobs relationship to Task.
    • Deprecated the requirements field in Task.
  • libs/core/kiln_ai/datamodel/test_eval_model.py
    • Added test cases for the migrate_train_set_filter_id functionality in the Eval model.
  • libs/core/kiln_ai/datamodel/test_prompt_optimization_job.py
    • Added test cases for the new PromptOptimizationJob data model.
  • libs/core/kiln_ai/datamodel/test_task.py
    • Added test cases for the prompt_optimization_jobs relationship in Task.
  • libs/server/kiln_server/prompt_api.py
    • Added generator_id to PromptCreateRequest.
    • Updated create_prompt to utilize generator_id.
    • Included created_at in ApiPrompt for task_run_config prompts.
    • Modified update_prompt to return ApiPrompt and handle non-editable prompt types.
    • Updated descriptions for prompt generators.
  • libs/server/kiln_server/test_prompt_api.py
    • Updated test cases for update_prompt to align with changes in handling non-custom prompts.
Activity
  • The pull request was opened by sfierro.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request polishes the UI for the Copilot connect flow by enhancing the Intro component to support markdown, which improves the descriptive text in copilot_required_card. It also adds helpful 'Learn More' links and clarifies text in the entitlement_required_card. My feedback points out a hardcoded URL in a generic component that could affect reusability. Overall, these are positive UI improvements.

Comment thread app/web_ui/src/lib/ui/kiln_copilot/entitlement_required_card.svelte

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 19

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
app/desktop/studio_server/api_client/kiln_ai_server_client/api/jobs/get_sample_job_result_v1_jobs_sample_job_job_id_result_get.py (1)

14-25: ⚠️ Potential issue | 🟡 Minor

Remove manual edit to auto-generated file.

This file is explicitly marked as auto-generated with a warning: "Important: This folder is automatically generated. Do not make any edits, they will be replaced on next generation." The blank line added at line 17 is a manual edit that will be silently lost on the next code generation run. Remove the blank line and avoid hand-editing files in api_client/kiln_ai_server_client/. If the API definition needs updating, regenerate the client from the source OpenAPI spec using the generation script.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/desktop/studio_server/api_client/kiln_ai_server_client/api/jobs/get_sample_job_result_v1_jobs_sample_job_job_id_result_get.py`
around lines 14 - 25, The file contains a manual blank-line edit inside the
auto-generated function _get_kwargs; remove the added blank line so the function
matches the generator output exactly and do not hand-edit files under the
api_client/kiln_ai_server_client folder; if you need changes to the endpoint URL
or signature, update the OpenAPI spec and re-run the client generation script
instead of modifying _get_kwargs or other generated functions.
app/web_ui/src/lib/ui/run_config_component/create_new_run_config_dialog.svelte (1)

74-80: ⚠️ Potential issue | 🟡 Minor

submit_label should reflect the current mode.

The submit button reads "Create" even in clone mode, which is misleading for users. Since dialog_title is already made reactive for the title, the same pattern should apply to the submit label.

🛠️ Proposed fix
+  $: submit_label = mode === "create" ? "Create" : "Clone"
   <FormContainer
     submit_visible={true}
-    submit_label="Create"
+    {submit_label}
     on:submit={create_new_run_config}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/web_ui/src/lib/ui/run_config_component/create_new_run_config_dialog.svelte`
around lines 74 - 80, Replace the hardcoded submit_label="Create" with a
reactive submit label tied to the dialog's mode (e.g. use
submit_label={submit_label} or submit_label={dialog_submit_label}) and add a
reactive statement that derives submit_label from the existing dialog_title (or
the mode variable) so the button reads "Clone" in clone mode and "Create" (or
another appropriate verb) in create mode; keep on:submit={create_new_run_config}
and other props unchanged.
app/desktop/studio_server/test_eval_api.py (1)

1644-1740: ⚠️ Potential issue | 🟡 Minor

Add assertion for the new train_dataset_size field in test_get_eval_progress.

The get_eval_progress endpoint now returns train_dataset_size (line 693 of eval_api.py), but the test does not verify this field. Add assert result["train_dataset_size"] == 0 (since mock_eval.train_set_filter_id is None, no train dataset is fetched).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/desktop/studio_server/test_eval_api.py` around lines 1644 - 1740, The
test test_get_eval_progress is missing an assertion for the new
train_dataset_size field returned by the get_eval_progress endpoint; update the
test_get_eval_progress function to assert result["train_dataset_size"] == 0
(mock_eval.train_set_filter_id is None so no train dataset is fetched) after the
existing response checks, keeping the rest of the mocks and assertions intact.
🧹 Nitpick comments (38)
app/web_ui/src/lib/ui/icons/star_icon.svelte (1)

6-17: LGTM — consider adding aria-hidden="true" for decorative usage.

The filled prop toggle and fill/stroke logic are correct. As this SVG is purely decorative, adding aria-hidden="true" prevents screen readers from announcing it as an unlabelled image. If the icon is ever used in a meaningful, interactive context, a wrapping <button> with aria-label would be the appropriate place for the label instead.

♿ Suggested accessibility improvement
 <svg
   class="w-full h-full"
   viewBox="0 0 24 24"
   fill={filled ? "currentColor" : "none"}
   xmlns="http://www.w3.org/2000/svg"
+  aria-hidden="true"
 >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/web_ui/src/lib/ui/icons/star_icon.svelte` around lines 6 - 17, The SVG in
star_icon.svelte is decorative; add aria-hidden="true" to the <svg> element (the
element using the filled prop and fill/stroke logic) so screen readers ignore
it, and ensure any interactive wrapper (e.g., a <button> that uses this icon)
provides an accessible name via aria-label or similar when the icon becomes
meaningful.
app/web_ui/src/routes/(app)/dataset/[project_id]/[task_id]/add_data/+page.svelte (1)

118-118: Redundant reason && guard — reason is always truthy.

reason is typed as Reason ("generic" | "eval" | "fine_tune") and is assigned via a reactive statement (lines 35–39) that always falls back to "generic" — it is never null, undefined, or an empty string. The reason && sub-expression is therefore a no-op and only the reason !== "generic" clause does real work.

♻️ Simplify both guards
-        if (reason && reason !== "generic") params.set("reason", reason)
+        if (reason !== "generic") params.set("reason", reason)
-      if (reason && reason !== "generic") params.set("reason", reason)
+      if (reason !== "generic") params.set("reason", reason)

Also applies to: 134-134

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/web_ui/src/routes/`(app)/dataset/[project_id]/[task_id]/add_data/+page.svelte
at line 118, The guard "reason &&" is redundant because reason is always a valid
Reason ("generic" | "eval" | "fine_tune"); remove the "reason &&" portion and
only check reason !== "generic" before calling params.set("reason", reason).
Update both occurrences that call params.set("reason", reason) so they use if
(reason !== "generic") params.set("reason", reason) (leaving all other logic
untouched).
app/web_ui/src/lib/utils/link_builder.test.ts (1)

80-107: Missing coverage for plain (no-::) prompt IDs after removing the generator_details fallback.

The "saved prompts (ID style)" group only tests prompt_id values containing ::. The core behavioral change — that prompts without :: (previously routed to generator_details) now also resolve to the saved path — has no test. Adding a case like "my-custom-prompt"/prompts/.../saved/my-custom-prompt would explicitly document and guard this new behaviour.

✅ Proposed additional test case
   describe("saved prompts (ID style)", () => {
+    it("routes plain (non-colon) prompt IDs to the saved path", () => {
+      const promptId = "my-custom-prompt"
+      const result = prompt_link(mockProjectId, mockTaskId, promptId)
+
+      expect(result).toBe(
+        `/prompts/${mockProjectId}/${mockTaskId}/saved/my-custom-prompt`,
+      )
+    })
+
     it("links to saved prompts for prompts with double colons", () => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/web_ui/src/lib/utils/link_builder.test.ts` around lines 80 - 107, Add a
unit test to cover plain saved prompt IDs without any "::" so the new behavior
(routing plain IDs to the saved path instead of generator_details) is asserted;
locate the "saved prompts (ID style)" describe block in link_builder.test.ts and
add a test that uses prompt_link(mockProjectId, mockTaskId, "my-custom-prompt")
and expects `/prompts/${mockProjectId}/${mockTaskId}/saved/my-custom-prompt`;
ensure the test name clearly states it handles plain (no-"::") prompt IDs and
mirrors the existing style of the other cases.
app/desktop/studio_server/api_client/kiln_ai_server_client/models/specification_input_spec_field_current_values.py (1)

1-16: Auto-generated attrs-based model — Pydantic guideline doesn't apply here.

This file follows the openapi-python-client generation pattern (@_attrs_define, TypeVar-bound from_dict/to_dict, additional_properties bag). The project guideline to use Pydantic v2 targets hand-authored data-validation code, not generated client stubs, so no action is required. Worth documenting this exclusion in the project guidelines if it isn't already.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/desktop/studio_server/api_client/kiln_ai_server_client/models/specification_input_spec_field_current_values.py`
around lines 1 - 16, This autogenerated attrs-based model (class
SpecificationInputSpecFieldCurrentValues with additional_properties) is
intentionally not migrated to Pydantic; add a short inline comment at the top of
this file explaining it's generated by openapi-python-client and is excluded
from the project's Pydantic-v2 guideline (or add that exclusion to the project
guidelines doc), so reviewers know this pattern is intentional and should not be
changed.
libs/core/kiln_ai/cli/commands/package_project.py (2)

786-796: _ignore_eval_config_runs relies on directory naming convention — consider adding a comment or assertion.

The callback assumes the on-disk hierarchy is exactly evals/{eval_id}/configs/{config_id}/runs/.... If a directory happens to be named configs at a different level, it would incorrectly strip its runs child. This is fine given the known Kiln directory structure, but a brief inline comment noting the expected path pattern would help future maintainers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/core/kiln_ai/cli/commands/package_project.py` around lines 786 - 796,
The helper _ignore_eval_config_runs assumes the directory path follows
evals/{eval_id}/configs/{config_id}/runs and thus only checks parent.name ==
"configs"; add a short inline comment stating the exact expected on-disk pattern
and add a defensive assertion or path check in _ignore_eval_config_runs (e.g.,
assert "evals" in dir_path.parents or verify parent.parent.name looks like an
eval id) to make the assumption explicit and fail-fast if the directory
structure differs; reference the function name _ignore_eval_config_runs and the
dir_path/parent.parent logic when adding the comment and assertion.

779-783: Consider using Pydantic BaseModel for consistency with the rest of the codebase.

The rest of the Kiln data model uses Pydantic. While a plain @dataclass works fine for this simple config, using BaseModel would maintain consistency and enable validation if fields grow in the future. As per coding guidelines, "Use Pydantic v2 (not v1) for data validation."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/core/kiln_ai/cli/commands/package_project.py` around lines 779 - 783,
Replace the dataclass with a Pydantic v2 model for consistency: remove the
`@dataclass` decorator and make PackageForTrainingConfig inherit from
pydantic.BaseModel (import BaseModel from pydantic), keep the same typed fields
and default values (include_documents, exclude_task_runs,
exclude_eval_config_runs), and adjust any usages if they relied on
dataclass-specific behavior; you may add a model_config dict if you need custom
validation/serialization options later.
libs/core/kiln_ai/cli/commands/test_package_project.py (2)

1560-1560: Remove the OUTPUT_TO_CWD debug flag.

This module-level constant appears to be a developer convenience for writing exported zips to CWD during local debugging (used on line 1737). It shouldn't be committed — if someone accidentally sets it to True, the test will write files outside tmp_path, polluting the working directory and potentially causing non-deterministic test behavior.

Proposed fix
-OUTPUT_TO_CWD = False
-
-
 class TestPackageProjectForTraining:
     def test_full_package_with_evals_and_docs(
         self, temp_project_with_evals, tmp_path: Path
     ):
         source = temp_project_with_evals
-        output_path = (
-            Path("./exported_training.zip")
-            if OUTPUT_TO_CWD
-            else tmp_path / "output" / "training.zip"
-        )
+        output_path = tmp_path / "output" / "training.zip"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/core/kiln_ai/cli/commands/test_package_project.py` at line 1560, Remove
the module-level debug flag OUTPUT_TO_CWD and any branch that writes exported
zips to the current working directory; instead always write test outputs to the
provided tmp_path. Locate uses of OUTPUT_TO_CWD (and the conditional logic that
writes to CWD, referenced where exported zips are created) and replace them so
the output path is derived only from the test's tmp_path fixture (or a local
variable defaulting to tmp_path), ensuring no code path writes to os.getcwd() or
CWD during tests.

1672-1704: Missing temp directory cleanup in TestExportEvals.

create_export_directory creates a tempfile.mkdtemp-based directory. Existing tests (e.g., TestExportTask, TestSavePromptToTask) wrap this in try/finally with shutil.rmtree. These tests don't, relying on tmp_path cleanup — but create_export_directory writes to its own tempfile.mkdtemp, not under tmp_path.

Example fix for `test_exports_selected_evals`
     def test_exports_selected_evals(self, temp_project_with_evals, tmp_path: Path):
         """Test that only selected eval IDs are exported with full subtree."""
+        import shutil
         source = temp_project_with_evals
-        _, exported_project = create_export_directory(source["project"])
-        exported_task, _ = export_task(
-            source["task"], source["run_config"], exported_project
-        )
+        temp_dir, exported_project = create_export_directory(source["project"])
+        try:
+            exported_task, _ = export_task(
+                source["task"], source["run_config"], exported_project
+            )
 
-        exported_ids = export_evals(source["task"], [source["eval1"].id], exported_task)
+            exported_ids = export_evals(source["task"], [source["eval1"].id], exported_task)
 
-        assert exported_ids == [source["eval1"].id]
-        exported_evals = exported_task.evals(readonly=True)
-        assert len(exported_evals) == 1
-        assert exported_evals[0].id == source["eval1"].id
+            assert exported_ids == [source["eval1"].id]
+            exported_evals = exported_task.evals(readonly=True)
+            assert len(exported_evals) == 1
+            assert exported_evals[0].id == source["eval1"].id
 
-        configs = exported_evals[0].configs(readonly=True)
-        assert len(configs) == 1
-        assert configs[0].id == source["eval1_config"].id
+            configs = exported_evals[0].configs(readonly=True)
+            assert len(configs) == 1
+            assert configs[0].id == source["eval1_config"].id
+        finally:
+            shutil.rmtree(temp_dir, ignore_errors=True)

Same applies to test_exports_no_evals_when_ids_dont_match, TestExportDocuments, and TestExportTaskRuns.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/core/kiln_ai/cli/commands/test_package_project.py` around lines 1672 -
1704, The tests in TestExportEvals (methods test_exports_selected_evals and
test_exports_no_evals_when_ids_dont_match) call create_export_directory which
returns a tempfile.mkdtemp-created directory that is not under tmp_path and must
be explicitly removed; update each test to wrap the export-directory creation
and subsequent assertions in a try/finally (or ensure cleanup) and call
shutil.rmtree on the directory returned by create_export_directory in the
finally block (referencing the local var holding the
exported_project/export_dir), similar to existing patterns in TestExportTask and
TestSavePromptToTask; do the same cleanup change for other tests mentioned
(TestExportDocuments, TestExportTaskRuns) that also use create_export_directory.
app/web_ui/src/lib/ui/float.svelte (1)

23-55: Prop changes to placement, offset_px, shift_padding, and strategy don't trigger repositioning.

autoUpdate adds listeners that automatically call an update function when the DOM layout changes (scroll, resize, ResizeObserver) — but not when Svelte props change. If a parent toggles placement at runtime, the float silently stays in its old position.

Additionally, changing strategy from fixed to absolute after mount updates the Tailwind class correctly but leaves cleanupAutoUpdate in place with stale listener configuration; the guard on Line 43 blocks a clean restart.

Add a reactive statement and a helper to restart autoUpdate on strategy changes:

♻️ Proposed fix
+  $: placement, strategy, offset_px, shift_padding, updatePosition()
+
+  $: if (strategy && contentElement) {
+    stopAutoUpdate()
+    startAutoUpdate()
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/web_ui/src/lib/ui/float.svelte` around lines 23 - 55, The component
currently never restarts autoUpdate when props like placement, offset_px,
shift_padding, or strategy change; add a small helper restartAutoUpdate that
calls stopAutoUpdate() then startAutoUpdate(), adjust startAutoUpdate so it
doesn't bail out when cleanupAutoUpdate already exists (or simply call
stopAutoUpdate before creating a new autoUpdate), and add a reactive statement
(e.g., $: restartAutoUpdate() or an equivalent Svelte reactive block) that
watches placement, offset_px, shift_padding, and strategy and calls
restartAutoUpdate so computePosition listeners are re-created with up-to-date
middleware and strategy; keep references to updatePosition, startAutoUpdate,
stopAutoUpdate, cleanupAutoUpdate, and autoUpdate when making the changes.
app/web_ui/src/lib/ui/feature_carousel.svelte (1)

69-69: Hardcoded text-gray-500 / bg-gray-200 break DaisyUI theming

These fixed Tailwind palette values won't respond to DaisyUI dark-mode or custom themes. Replacing them with DaisyUI semantic tokens keeps the component theme-aware.

♻️ Proposed fix
-            <div class="text-xs text-gray-500 font-medium mt-1">
+            <div class="text-xs text-base-content/50 font-medium mt-1">
-                    <span class="text-gray-500 w-24 text-xs">{label}</span>
+                    <span class="text-base-content/50 w-24 text-xs">{label}</span>
-                          class="h-3 rounded-full flex-1 {i < value
+                          class="h-3 rounded-full flex-1 {i < value
                             ? 'bg-secondary'
-                            : 'bg-gray-200'}"
+                            : 'bg-base-300'}"

As per coding guidelines, DaisyUI should be used for UI components in the web frontend; semantic DaisyUI tokens (base-content, base-300) are the appropriate replacements for raw Tailwind palette grays.

Also applies to: 82-82, 88-88

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/web_ui/src/lib/ui/feature_carousel.svelte` at line 69, Replace hardcoded
Tailwind gray utilities in the FeatureCarousel component with DaisyUI semantic
tokens so themes/dark mode work: update the class "text-xs text-gray-500
font-medium mt-1" to use "text-xs text-base-content font-medium mt-1" and
replace any "bg-gray-200" instances (and the other occurrences noted around
lines 82 and 88) with "bg-base-300" (or the appropriate DaisyUI semantic bg
token) in the feature_carousel.svelte markup to make the component theme-aware.
libs/server/kiln_server/prompt_api.py (1)

103-111: Guard against duplicate created_at key if BasePrompt gains the field.

Currently BasePrompt does not have created_at, so task_run_config.prompt.model_dump(exclude={"id"}) is safe. However, if BasePrompt is ever extended with created_at, unpacking **properties alongside the explicit created_at=task_run_config.created_at on line 108 would raise TypeError: multiple values for keyword argument 'created_at'.

A defensive fix would exclude created_at from the dump:

🛡️ Defensive fix
-                properties = task_run_config.prompt.model_dump(exclude={"id"})
+                properties = task_run_config.prompt.model_dump(exclude={"id", "created_at"})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/server/kiln_server/prompt_api.py` around lines 103 - 111, The ApiPrompt
construction may get duplicate created_at if BasePrompt later adds that field;
update the call that builds properties from task_run_config.prompt.model_dump to
exclude "created_at" (in addition to "id") so that when creating ApiPrompt with
created_at=task_run_config.created_at you won't pass duplicate keyword args;
modify the model_dump(exclude=...) invocation used before ApiPrompt(...) to
include "created_at" in the exclusion.
libs/core/kiln_ai/datamodel/test_task.py (1)

408-408: Deferred PromptOptimizationJob import inconsistent with rest of file

PromptOptimizationJob is imported inside both test functions, while all other types in this file (Spec, Task, RunConfigProperties, etc.) are imported at the module top. Since PromptOptimizationJob is now exported from kiln_ai.datamodel, there's no circular-import reason to defer it.

♻️ Suggested fix — move import to top of file
 from kiln_ai.datamodel.task import RunConfigProperties, Task, TaskRunConfig
 from kiln_ai.datamodel.task_output import normalize_rating
+from kiln_ai.datamodel import PromptOptimizationJob

Then remove both inline from kiln_ai.datamodel import PromptOptimizationJob lines inside the test bodies.

Also applies to: 432-432

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/core/kiln_ai/datamodel/test_task.py` at line 408, Move the deferred
import of PromptOptimizationJob to the module top alongside the other model
imports (i.e., add "from kiln_ai.datamodel import PromptOptimizationJob" with
the existing top-level imports) and remove the two inline "from
kiln_ai.datamodel import PromptOptimizationJob" lines inside the test functions
(the inline imports currently present around lines referenced by the review).
This keeps imports consistent with Spec/Task/RunConfigProperties and avoids
duplicate imports in the test bodies.
libs/core/kiln_ai/datamodel/prompt_optimization_job.py (1)

25-28: latest_status accepts any string — consider constraining to known values

The docstring documents five legal values (pending, running, succeeded, failed, cancelled) but the field is typed as plain str, allowing arbitrary values to be stored silently. Literal would make typos and unexpected statuses visible at the type-checker level without adding runtime cost.

♻️ Suggested change
+from typing import Literal, TYPE_CHECKING
...
-    latest_status: str = Field(
+    latest_status: Literal["pending", "running", "succeeded", "failed", "cancelled"] = Field(
         default="pending",
         description="The latest known status of this prompt optimization job (pending, running, succeeded, failed, cancelled). Not updated in real time.",
     )

If the remote server can return statuses not in this list, keep str but add a note explaining why.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/core/kiln_ai/datamodel/prompt_optimization_job.py` around lines 25 - 28,
The latest_status Field currently typed as str allows arbitrary values; change
its annotation to use typing.Literal with the five allowed values
(Literal["pending", "running", "succeeded", "failed", "cancelled"]) so
type-checkers will catch invalid statuses, and keep the Field(...) call but
remove any conflicting runtime validation; alternatively, if the remote API can
return other statuses, leave latest_status as str but add a clarifying
comment/docstring on the field explaining why it remains unconstrained; refer to
the latest_status declaration in prompt_optimization_job.py when making this
change.
app/web_ui/src/lib/utils/name_generator.ts (1)

1-52: Optional: use as const for immutable vocabulary arrays.

The string[] annotation is redundant (TypeScript infers it) and allows accidental runtime mutation of the arrays. as const would give readonly tuples and prevent mutation at zero cost.

♻️ Proposed refactor
-const ADJECTIVES: string[] = [
+const ADJECTIVES = [
   "Curious",
   ...
   "Imperial",
-]
+] as const

-const NOUNS: string[] = [
+const NOUNS = [
   "Penguin",
   ...
   "Ivy",
-]
+] as const

The index expressions Math.floor(Math.random() * ADJECTIVES.length) work identically on readonly tuples.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/web_ui/src/lib/utils/name_generator.ts` around lines 1 - 52, Replace the
mutable typed arrays (e.g., the ADJECTIVES declaration) with readonly tuple
literals using "as const" instead of "string[]" to prevent runtime mutation and
remove the redundant explicit type; for example change the ADJECTIVES
declaration to a const initialized array with "as const" (and do the same for
any sibling vocabulary arrays in this file), leaving existing index usages like
Math.floor(Math.random() * ADJECTIVES.length) unchanged since readonly tuples
support the same operations.
app/web_ui/src/lib/ui/markdown_block.svelte (1)

71-73: Use <div> instead of <p> for the blank-line spacer.

An empty <p> is semantically a paragraph and may accumulate browser default margin-top/margin-bottom that conflicts with the h-2 height. A <div class="h-2"></div> avoids this and better represents a pure visual spacer.

♻️ Proposed fix
-    <p class="h-2"></p>
+    <div class="h-2"></div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/web_ui/src/lib/ui/markdown_block.svelte` around lines 71 - 73, Replace
the empty paragraph used as a spacer in the {:else} branch of
markdown_block.svelte with a neutral block element: change the <p
class="h-2"></p> spacer to a <div class="h-2"></div> so it no longer implies
paragraph semantics or inherits paragraph margins; locate the {:else} branch in
the template and update that element accordingly.
app/web_ui/src/lib/ui/run_config_component/run_config_summary.svelte (1)

26-37: Consider using a native <button> instead of <div role="button">.

A native <button> provides keyboard interaction (Enter/Space), focus ring, and correct semantics without the manual tabindex, role, and on:keydown wiring. This also eliminates the need for the explicit e.preventDefault() on Space.

♻️ Proposed refactor
-<div
-  class="cursor-pointer hover:bg-base-200 rounded-lg p-4"
-  tabindex="0"
-  aria-label="Open Run Configuration"
-  role="button"
-  on:click={openRunConfig}
-  on:keydown={(e) => {
-    if (e.key === "Enter" || e.key === " ") {
-      e.preventDefault()
-      openRunConfig()
-    }
-  }}
->
+<button
+  class="cursor-pointer hover:bg-base-200 rounded-lg p-4 w-full text-left"
+  aria-label="Open Run Configuration"
+  on:click={openRunConfig}
+>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/web_ui/src/lib/ui/run_config_component/run_config_summary.svelte` around
lines 26 - 37, Replace the interactive div in run_config_summary.svelte with a
native <button type="button"> element: keep the class="cursor-pointer
hover:bg-base-200 rounded-lg p-4", aria-label="Open Run Configuration", and the
on:click={openRunConfig} binding (openRunConfig is the click handler), but
remove tabindex, role, and the on:keydown handler and its e.preventDefault()
logic since the native button provides correct keyboard behavior and focus
styling automatically; ensure the button element retains any styling and
accessibility attributes currently on the div.
app/web_ui/src/lib/ui/run_config_component/create_new_run_config_dialog.svelte (1)

25-25: Minor: initial run_config_name generation at module load is immediately overridden.

generate_memorable_name() at line 25 runs once on module instantiation but is overridden on every show() / showClone() call before the dialog is shown. Consider initializing lazily:

♻️ Proposed refactor
-  let run_config_name: string = generate_memorable_name()
+  let run_config_name: string = ""

The name is unconditionally assigned inside both show() and showClone() before dialog?.show(), so the initial value is never used.

Also applies to: 33-47

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/web_ui/src/lib/ui/run_config_component/create_new_run_config_dialog.svelte`
at line 25, Initial memorable name is generated at module load but always
overwritten by show() / showClone(); change run_config_name initialization to
lazy (e.g., empty string or undefined) and call generate_memorable_name() only
inside show() and showClone() when a name isn't provided. Update references to
run_config_name in create_new_run_config_dialog.svelte so show() and showClone()
set run_config_name = generate_memorable_name() only if it's falsy (or when
cloning behavior requires it), removing the needless module-level
generate_memorable_name() call.
app/web_ui/src/routes/(app)/specs/[project_id]/[task_id]/+page.svelte (1)

44-45: Consider decoupling settings_loading from the primary page loading state.

settings_loading (the copilot check) is included in $: loading, causing the full page spinner to block until the copilot check resolves. Since has_kiln_copilot is only needed for the "Create Spec" action, specs and evals could render independently while the copilot check happens in the background.

♻️ Proposed refactor
- $: loading = specs_loading || evals_loading || settings_loading
+ $: loading = specs_loading || evals_loading

The has_kiln_copilot flag can stay false (its default) until the async check resolves; the "Create Spec" button will simply use the fallback branch until then, with no visible breakage.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/web_ui/src/routes/`(app)/specs/[project_id]/[task_id]/+page.svelte around
lines 44 - 45, The page-level reactive `loading` currently includes
`settings_loading` and blocks the full-page spinner until the copilot check
finishes; remove `settings_loading` from the page loading aggregation (change
the reactive `$: loading = specs_loading || evals_loading`) so specs/evals can
render independently, and keep `settings_loading` (and `has_kiln_copilot`) only
for the Create Spec UI (use `settings_loading` to disable/show spinner on the
Create Spec button or to fall back to the default branch while the async check
completes), ensuring `has_kiln_copilot` remains false by default until the async
check resolves.
app/web_ui/src/routes/(app)/prompt_optimization/[project_id]/[task_id]/prompt_optimization_job/[job_id]/+page.svelte (2)

27-54: Minor: polling_timer type and variable name suggest setTimeout but setInterval is used.

The variable is named polling_timer and typed as ReturnType<typeof setTimeout>, but lines 44 and 51 use setInterval / clearInterval. While the return type is identical at runtime (both are number in browsers), consider typing it as ReturnType<typeof setInterval> for clarity.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/web_ui/src/routes/`(app)/prompt_optimization/[project_id]/[task_id]/prompt_optimization_job/[job_id]/+page.svelte
around lines 27 - 54, The polling_timer is typed and named as if for setTimeout
but start_polling() uses setInterval/clearInterval; update the declaration of
polling_timer to use ReturnType<typeof setInterval> (and consider renaming to
pollingInterval or similar) and ensure start_polling() and stop_polling() keep
using setInterval/clearInterval consistently so the type and name match the
actual usage in start_polling and stop_polling.

183-259: build_properties is called imperatively—properties won't reactively update if run_configs or evals change independently.

build_properties() is only invoked from get_prompt_optimization_job (line 94). If the run configs store updates outside that flow, the properties list will be stale. Consider making properties a reactive $: derivation, or at minimum documenting this is intentional. This is acceptable for a detail page but worth noting.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/web_ui/src/routes/`(app)/prompt_optimization/[project_id]/[task_id]/prompt_optimization_job/[job_id]/+page.svelte
around lines 183 - 259, build_properties currently runs only when
get_prompt_optimization_job is called so `properties` can become stale if
`run_configs` or `eval_ids` change later; make `properties` reactive instead by
replacing the imperative call pattern with a Svelte reactive statement that
recomputes properties whenever any of prompt_optimization_job, run_configs, or
prompt_optimization_job.eval_ids change (or alternatively call build_properties
inside a reactive $: block that depends on those values), referencing the
existing build_properties logic (or moving its body into the reactive block) so
UI updates automatically without relying solely on get_prompt_optimization_job.
app/web_ui/src/routes/(app)/prompts/[project_id]/[task_id]/prompt_generators/prompt_generators.ts (1)

102-116: Consider returning a more informative fallback when generator_id is provided but unrecognized.

When generator_id is truthy but not found in prompt_generator_categories, the function silently falls through to the "id::" / "Unknown" checks. If a generator_id was explicitly supplied, the caller likely expects a generator-based label. This is a minor edge case—current behavior is safe—but a fallback like generator_id itself or "Custom Generator" might be more informative.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/web_ui/src/routes/`(app)/prompts/[project_id]/[task_id]/prompt_generators/prompt_generators.ts
around lines 102 - 116, getPromptType currently falls through to other checks
when a truthy generator_id isn't found in prompt_generator_categories; change
its fallback so if generator_id is provided but no matching template is found,
it returns a more informative label (e.g., the generator_id value or a fixed
string like "Custom Generator") instead of silently continuing to the prompt_id
checks; update the logic around the generator lookup in getPromptType to return
that informative fallback when generator_id is truthy and .find(...) yields no
result so callers can see the intended generator context.
app/web_ui/src/routes/(app)/models/price.ts (2)

26-61: Failed fetches are permanently cached — no retry on transient errors.

Once pricingLoadPromise is set, subsequent calls return the same (possibly null) promise. If the first call fails due to a transient network issue or timeout, pricing data will remain null for the entire session with no way to retry.

Consider clearing pricingLoadPromise on failure so the next caller triggers a fresh attempt:

Proposed fix
   pricingLoadPromise = (async () => {
     try {
       const controller = new AbortController()
       const timeoutId = setTimeout(() => controller.abort(), 5000)
       // ...fetch logic...
     } catch {
+      pricingLoadPromise = null   // allow retry on next call
       return null
     }
   })()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/web_ui/src/routes/`(app)/models/price.ts around lines 26 - 61, The
current fetchPricingData implementation caches a failing result in
pricingLoadPromise so transient errors are never retried; update
fetchPricingData to clear/reset pricingLoadPromise and leave pricingData
unchanged when a fetch fails or returns invalid data (i.e., on non-ok response,
invalid JSON shape, or in the catch block) so subsequent calls will create a new
promise; keep the successful path setting pricingData and resolving
pricingLoadPromise, and ensure any cleanup (clearTimeout on timeoutId) still
runs before resetting the cached promise.

48-52: Weak runtime validation before the as PricingData cast.

The check typeof data === "object" && data !== null accepts any object shape (e.g., [], {foo: 42}). Since the external API is third-party and the shape could change, consider validating a bit more defensively — e.g., confirming the top-level values contain a models key — to avoid silent misbehavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/web_ui/src/routes/`(app)/models/price.ts around lines 48 - 52, The
runtime guard is too weak before casting to PricingData; strengthen validation
of the fetched `data` (used to set `pricingData`) by asserting expected
top-level shape—e.g., check `data` is an object, has a `models` property and
that `models` is an array (or object) with expected items, and optionally
validate other top-level keys your PricingData requires—before doing
`pricingData = data as PricingData` in this module (look for the `pricingData`,
`PricingData`, and `data` symbols). If validation fails, handle the error path
(throw or return a safe default) instead of casting blindly.
app/web_ui/src/routes/(app)/models/+page.svelte (1)

598-598: Pricing fetch blocks the entire page render.

pricingLoading is included in the loading gate (loading || pricingLoading), so the spinner stays until the external models.dev fetch completes (or times out after 5s). Since pricing is supplementary, consider loading the page immediately and letting prices fill in asynchronously.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/web_ui/src/routes/`(app)/models/+page.svelte at line 598, The page's main
loading gate uses pricingLoading so the spinner waits for the external
models.dev pricing fetch; change the render condition to only depend on the
primary page loader (replace or remove pricingLoading from the {`#if` loading ||
pricingLoading} check), and move pricingLoading handling into the pricing
display component/section so prices are fetched and rendered asynchronously with
their own placeholder or loading indicator; update references to pricingLoading
and the pricing renderer in +page.svelte so the page renders immediately while
prices fill in when ready.
app/web_ui/src/routes/(app)/prompt_optimization/[project_id]/[task_id]/create_prompt_optimization_job/+page.svelte (2)

565-575: Reactive async call can race when target_run_config_id changes rapidly.

If the user toggles run configs quickly, multiple check_run_config_validation() calls can be in-flight concurrently, and the last one to resolve (not the last one started) writes the final state. Consider guarding with an incrementing request counter or AbortController to ignore stale results.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/web_ui/src/routes/`(app)/prompt_optimization/[project_id]/[task_id]/create_prompt_optimization_job/+page.svelte
around lines 565 - 575, The reactive block that calls
check_run_config_validation() can produce stale state when target_run_config_id
changes rapidly; update either the reactive block or the async function
check_run_config_validation to ignore outdated responses by adding a
cancellation/serial-id guard: introduce a module-scoped counter (e.g.,
runConfigValidationRequestId) or an AbortController that you increment/create
before each call, pass the id/controller into check_run_config_validation, and
inside that function only commit to run_config_validation_status,
run_config_validation_message, and run_config_blocking_reason if the request id
matches the latest or the controller is not aborted; ensure you also abort or
ignore previous in-flight requests when starting a new one.

590-657: refresh_evaluators only updates existing evals — new server-side evals won't appear.

The refresh maps over the current evals_with_configs list, so any evals created since the page loaded are silently excluded. If that's intentional, a brief comment would help; otherwise, consider merging the fresh evals_data list with the existing one to surface new evals.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/web_ui/src/routes/`(app)/prompt_optimization/[project_id]/[task_id]/create_prompt_optimization_job/+page.svelte
around lines 590 - 657, refresh_evaluators currently only remaps the existing
evals_with_configs so any evals returned in evals_data that weren't present in
evals_with_configs are dropped; change it to merge fresh evals_data with the
existing evals_with_configs so new server-side evals appear. Concretely: in
refresh_evaluators use evals_data to drive the resulting list (iterate
evals_data), look up any existing entry in evals_with_configs by id to reuse
configs/current_config when present, and for evals without local configs fetch
their configs (same GET to /eval/{eval_id}/eval_configs) and build new items
with validation_status reset and errors null; finally set evals_with_configs to
that merged list and still call check_eval_validation for each index. Ensure you
update usages of evals_with_configs, evals_data, and check_eval_validation
accordingly.
app/desktop/studio_server/prompt_optimization_job_api.py (4)

183-203: Dead code: artifact_id on line 196 is assigned but never used after successful deletion.

The variable is only used in the except block (line 198) for logging. In the success path (line 196), it's unused.

Proposed fix
     try:
         artifact.delete()
-        artifact_id = getattr(artifact, "id", "unknown")
     except Exception as cleanup_error:
         artifact_id = getattr(artifact, "id", "unknown")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/desktop/studio_server/prompt_optimization_job_api.py` around lines 183 -
203, In _cleanup_artifact, the artifact_id is assigned after a successful delete
but never used; move the artifact_id = getattr(artifact, "id", "unknown") line
to before the try block (so both the success and exception paths can reference
it) and remove the redundant assignment inside the try; keep artifact.delete()
in the try and retain the logger.error call in the except using artifact_id and
exc_info=True.

330-381: check_run_config and check_eval endpoints expose internal exception messages to clients.

Lines 379 and 451 include str(e) in the HTTP error detail. Internal exception messages may leak implementation details (stack traces, file paths, credentials in URLs). Consider returning a generic message or sanitizing the error.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/desktop/studio_server/prompt_optimization_job_api.py` around lines 330 -
381, The catch-all exception handlers in check_run_config and check_eval
currently return HTTPException details containing str(e), which can leak
internals; change both except Exception blocks to log the full error with
logger.error(..., exc_info=True) but raise a generic HTTPException (e.g.,
status_code=500, detail="Internal server error while checking run config" for
check_run_config and "Internal server error while checking eval" for check_eval)
instead of including str(e); keep the internal logging so diagnostics remain
available but avoid exposing exception messages to clients.

279-327: Status update errors are silently swallowed — job returned with potentially stale status.

update_prompt_optimization_job_and_create_artifacts catches all exceptions at line 322 and returns the job without re-raising. This is intentional for the batch-update use case (line 588), but for the single-job get_prompt_optimization_job endpoint (line 628), the caller also swallows exceptions. The user gets a 200 with a stale status and no indication of failure. Consider at least logging at warning level or returning a flag indicating the status might be stale.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/desktop/studio_server/prompt_optimization_job_api.py` around lines 279 -
327, The function update_prompt_optimization_job_and_create_artifacts currently
swallows all exceptions and returns possibly stale PromptOptimizationJob; change
it to mark failures explicitly and increase visibility: inside the except block
of update_prompt_optimization_job_and_create_artifacts, log a warning
(logger.warning) that includes the exception and job_id, and set a clear flag on
the returned object (e.g., prompt_optimization_job.status_maybe_stale = True or
prompt_optimization_job.stale = True) so callers like
get_prompt_optimization_job can detect and surface stale status to users; keep
the batch-use behavior (do not re-raise) but ensure the new flag is persisted
via prompt_optimization_job.save_to_file() so callers can read it.

491-528: ZIP File object is created inside the with block but consumed outside it — works here, but the indentation boundary is easy to misread.

project_zip_file wraps an in-memory BytesIO (not a reference to the temp file), so using it after the TemporaryDirectory exits is safe. However, body construction and the API call at lines 518–528 are outside the with block while relying on project_zip_file. Consider moving the API call inside the with block to make the lifecycle relationship clearer, or adding a brief comment.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/desktop/studio_server/prompt_optimization_job_api.py` around lines 491 -
528, The File wrapper project_zip_file is created from in-memory bytes inside
the TemporaryDirectory context but is then used after that with
BodyStartPromptOptimizationJobV1JobsPromptOptimizationJobStartPost and in the
call to
start_prompt_optimization_job_v1_jobs_prompt_optimization_job_start_post.asyncio_detailed;
to make the lifecycle explicit either move the body construction and the
asyncio_detailed API call into the same with tempfile.TemporaryDirectory(...)
block so the creation and use are colocated, or keep them outside but add a
concise comment stating that project_zip_file holds in-memory BytesIO and is
safe after the temp dir exits; update references to project_zip_file,
BodyStartPromptOptimizationJobV1JobsPromptOptimizationJobStartPost, and
start_prompt_optimization_job_v1_jobs_prompt_optimization_job_start_post.asyncio_detailed
accordingly.
app/web_ui/src/routes/(app)/prompts/[project_id]/[task_id]/+page.svelte (2)

58-83: Sorting recomputes getPromptType on every reactive update.

sorted_prompts calls getPromptType(a.id, a.generator_id) for every comparison during sort. If getPromptType is non-trivial, this could be inefficient for large prompt lists since comparisons happen O(n log n) times. Consider pre-computing the type values before sorting.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/web_ui/src/routes/`(app)/prompts/[project_id]/[task_id]/+page.svelte
around lines 58 - 83, The sort recomputes getPromptType repeatedly inside the
sorted_prompts reactive block causing O(n log n) calls; precompute type strings
once before sorting (inside the same reactive block) by mapping prompts to a
temporary array or a Map keyed by prompt id (use getPromptType(a.id,
a.generator_id).toLowerCase() once per prompt), then use those cached values in
the comparator for the "type" case; update the comparator to read from the cache
and perform comparisons using the precomputed lowercase strings so sorting no
longer calls getPromptType for each comparison.

85-88: Use a query parameter instead of sessionStorage for passing the prompt text.

The sessionStorage approach with a magic string key is inconsistent with the established pattern in this codebase, which uses query parameters extensively for inter-page data passing (e.g., goto(...?${params.toString()}). While cleanup does occur in the consuming page, using a query parameter would be more maintainable, type-safe, and aligned with project conventions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/web_ui/src/routes/`(app)/prompts/[project_id]/[task_id]/+page.svelte
around lines 85 - 88, Replace the sessionStorage-based handoff in
handleSetBasePrompt with a query parameter: stop writing to sessionStorage using
the "pending_base_prompt" key and instead build a URLSearchParams (e.g.,
params.set('base_prompt', prompt_text)) and call
goto(`/prompts/${project_id}/${task_id}/edit_base_prompt?${params.toString()}`);
update the consuming page (+page.svelte for edit_base_prompt) to read and
validate the base_prompt query param rather than expecting sessionStorage, and
remove the sessionStorage key usage from handleSetBasePrompt.
app/web_ui/src/routes/(app)/+layout.svelte (2)

234-325: Optimize parent link won't show active state when a child route is active.

When the user navigates to /prompts/..., /models, /fine_tune/..., or /docs/..., the respective child gets the active class, but the parent "Optimize" <a> (line 237) only activates on /optimize/.... This means the parent nav item won't visually indicate that the user is within its subtree. Consider adding a check like:

[Section.Optimize, Section.Prompts, Section.Models, Section.FineTune, Section.Documents].includes(section)

for the parent's active class, so it highlights when any child is active.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/web_ui/src/routes/`(app)/+layout.svelte around lines 234 - 325, The
parent "Optimize" link only checks section == Section.Optimize, so it won't show
active when any child route is active; update the class binding on the Optimize
<a> (the element using section and Section) to check whether section is one of
the subtree values (e.g. Section.Optimize, Section.Prompts, Section.Models,
Section.FineTune, Section.Documents) by using an includes-style test or boolean
ORs so the parent receives "active" whenever any child section is active.

396-402: Global CSS selectors may have unintended side effects.

:global(ul > li.menu-nested) and :global(.menu li > ul) apply broadly to all matching elements across the app. If other pages introduce similar DaisyUI menu structures, these overrides could cause unexpected styling. Consider scoping these rules more narrowly if feasible.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/web_ui/src/routes/`(app)/+layout.svelte around lines 396 - 402, The
global CSS selectors :global(ul > li.menu-nested) and :global(.menu li > ul) are
too broad; scope them to the component by adding/using a unique wrapper class
(e.g., .app-menu, .sidebar or .chat-menu) on the parent container and update the
selectors to target that wrapper (for example :global(.app-menu ul >
li.menu-nested) and :global(.app-menu .menu li > ul)). Locate the style block in
+layout.svelte and replace the two global selectors with scoped variants that
include the chosen wrapper class so the rules only affect this component's menu.
app/web_ui/src/routes/(app)/prompts/[project_id]/[task_id]/saved/[prompt_id]/+page.svelte (1)

24-28: getPromptType called with empty string when prompt_model?.id is undefined.

If prompt_model is undefined (loading or not found), getPromptType("", null) is called during the reactive computation. This won't cause a runtime error since the result is filtered out by the .filter() on line 31, but it's a minor inefficiency. Not a blocker given the filter handles it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/web_ui/src/routes/`(app)/prompts/[project_id]/[task_id]/saved/[prompt_id]/+page.svelte
around lines 24 - 28, Avoid calling getPromptType with an empty string when
prompt_model is undefined by making the Type field conditional: only call
getPromptType when prompt_model?.id exists (e.g., use a guarded expression like
prompt_model?.id ? getPromptType(prompt_model.id, prompt_model.generator_id ??
null) : undefined). Update the object that sets Description/Type (referencing
prompt_model and getPromptType) so Type becomes undefined when prompt_model is
missing, preventing the unnecessary getPromptType("") call.
app/web_ui/src/routes/(app)/optimize/[project_id]/[task_id]/+page.svelte (2)

87-108: load_available_tools is fire-and-forget (not awaited).

Line 89 calls load_available_tools(project_id) without await and outside the Promise.all. If it fails, the error is silently swallowed. The getToolsDisplay function gracefully degrades to "Loading...", so this isn't a blocker, but an unhandled rejection could still surface in the console. Consider either awaiting it inside Promise.all or adding a .catch().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/web_ui/src/routes/`(app)/optimize/[project_id]/[task_id]/+page.svelte
around lines 87 - 108, The call to load_available_tools in the onMount block is
fire-and-forget and can produce unhandled rejections; update the onMount async
logic to either include load_available_tools(project_id) in the Promise.all with
the other async calls (so it is awaited) or append a .catch(...) to handle
errors explicitly, ensuring any thrown error is converted to a Kiln error
(createKilnError) or logged via existing error handling; locate the onMount
function and the Promise.all invocation and modify the call to
load_available_tools accordingly.

298-326: The "Compare" navigation button is visible outside the select_mode conditional.

The {#if selected_run_configs.size > 0} block (line 321) is a sibling to the {#if select_mode} block, not nested inside it. Currently this is safe because cancelSelection clears the set when exiting select mode. However, the intent would be clearer if the navigation Compare button were nested inside the select_mode branch to avoid any edge-case where selections leak.

♻️ Suggested restructure
           {`#if` select_mode}
             <div class="font-light text-sm">
               {selected_run_configs.size} selected{`#if` selected_run_configs.size >= MAX_SELECTIONS}
                 <span class="text-gray-400">{` (max)`}</span>
               {/if}
             </div>
             <button class="btn btn-mid" on:click={cancelSelection}>
               Cancel Selection
             </button>
+            {`#if` selected_run_configs.size > 0}
+              <button class="btn btn-primary btn-mid" on:click={handleCompare}>
+                Compare
+              </button>
+            {/if}
           {:else}
             <button class="btn btn-mid" on:click={() => (select_mode = true)}>
               Compare
             </button>
             <button
               class="btn btn-mid"
               on:click={() => create_run_config_dialog?.show()}
             >
               Create Run Config
             </button>
           {/if}
-          {`#if` selected_run_configs.size > 0}
-            <button class="btn btn-primary btn-mid" on:click={handleCompare}>
-              Compare
-            </button>
-          {/if}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/web_ui/src/routes/`(app)/optimize/[project_id]/[task_id]/+page.svelte
around lines 298 - 326, The Compare button rendered by the {`#if`
selected_run_configs.size > 0} block should be moved inside the {`#if`
select_mode} branch so it only appears when select_mode is active; update the
template so the selected count, Cancel Selection button and the Compare
navigation button (which calls handleCompare) are all inside the select_mode
branch, leaving the non-select-mode buttons (Compare to enter select_mode and
Create Run Config which calls create_run_config_dialog?.show()) in the {:else}
branch; this prevents leakage from selected_run_configs and keeps behavior tied
to the select_mode state (references: select_mode, selected_run_configs,
cancelSelection, handleCompare, create_run_config_dialog).
app/web_ui/src/routes/(app)/prompt_optimization/[project_id]/[task_id]/+page.svelte (1)

81-88: In-place .sort() mutates the response array directly.

prompt_optimization_jobs_response.sort(...) mutates the API response array in place. While functionally fine here since the result is immediately assigned, using [...prompt_optimization_jobs_response].sort(...) would be more defensive and consistent with the pattern in the optimize page's sortRunConfigs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/web_ui/src/routes/`(app)/prompt_optimization/[project_id]/[task_id]/+page.svelte
around lines 81 - 88, The code currently calls
prompt_optimization_jobs_response.sort(...) which mutates the API response
array; change this to sort a shallow copy instead (e.g., use a spread copy
before calling sort) so sorted_prompt_optimization_jobs is derived from a new
array and the original prompt_optimization_jobs_response remains unchanged—apply
this change where sorted_prompt_optimization_jobs is constructed (same pattern
as sortRunConfigs) and then assign the result to prompt_optimization_jobs.

Comment thread app/desktop/studio_server/prompt_optimization_job_api.py
Comment thread app/web_ui/src/lib/ui/edit_dialog.svelte
Comment thread app/web_ui/src/lib/ui/feature_carousel.svelte
Comment thread app/web_ui/src/lib/ui/float.svelte
Comment thread app/web_ui/src/lib/ui/float.svelte
Comment thread libs/core/kiln_ai/cli/commands/package_project.py
@leonardmq

Copy link
Copy Markdown
Collaborator

CI failing due to TODO comment in the code - otherwise ok

@sfierro sfierro merged commit 662c03b into sfierro/optimize-feature Feb 19, 2026
11 of 14 checks passed
@sfierro sfierro deleted the sfierro/copilot-connect-polish branch February 19, 2026 10:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants