# Turn Linear Issues Into Pull Requests for a Vue App

Yes. Label a Linear issue and an agent implements it in your Vue 3 app's pages, store or locales, adds a test, runs the CI checks and opens a linked pull request.

[Run this on Rush](https://cloud.getrush.ai/run/linear-issue-to-pr/vue/), in a Project that has your repository.

## What the agent does

The agent reads the Linear issue and its comments, then maps the ask onto the app's folders before it writes anything. In an app like antfu-collective/vitesse, a feature is spread across `src/pages/` (file-based routes, with the layout chosen in each page's `<route lang="yaml">` block), `src/layouts/` (`default.vue`, `home.vue`, `404.vue`), `src/components/` (auto-registered), `src/composables/` and `src/stores/` (auto-imported through `unplugin-auto-import` as `vite.config.ts` configures), and `src/modules/`, where `i18n.ts`, `pinia.ts`, `pwa.ts` and `nprogress.ts` each export an `install` that `src/main.ts` applies to the `ViteSSG` app. A request for a new screen is a new file under `src/pages/`; a request for shared state is the Pinia store in `src/stores/user.ts`; a request for new copy is keys in `locales/en.yml`, since templates render text through `t()`.

It follows the conventions the files already show. Styling is UnoCSS attributes on elements, as `TheInput.vue` sets `p="x-4 y-2"` and `border="~ rounded gray-200 dark:gray-700"`, and icons are classes such as `i-carbon-campsite`. Pages set their title with `useHead` and translate it with `t()`, as `index.vue` and `hi/[name].vue` do. A new component is written as `<script setup lang="ts">` with `defineProps`, like `TheCounter.vue`, and becomes usable in any template without an import once `src/components.d.ts` is regenerated. The dark toggle is `toggleDark` from `src/composables/dark.ts`, and the language switch is `toggleLocales` in `TheFooter.vue`, which cycles `availableLocales` from `src/modules/i18n.ts`. Anything the issue asks for that touches `window` or `document` must guard for server rendering, because `vite-ssg build` renders every page at build time.

The test goes where the behaviour can be seen. A store or composable change gets a Vitest case in `test/` with a fresh Pinia, matched by `test/**/*.test.ts` in a `jsdom` environment. A component gets a `mount` test in the style of `test/component.test.ts`, which passes props, reads `wrapper.text()`, triggers clicks on `.inc` and `.dec` and keeps a snapshot in `test/__snapshots__/`. A new page or navigation gets a case in `cypress/e2e/basic.spec.ts`, which visits `http://localhost:3333`, asserts exact URLs, types into `#input` and clicks `[data-test-id="about"]`; a new interactive element gets a `data-test-id` for that reason. The agent writes the smallest test that proves the issue's acceptance criteria and names the layer in the pull request.

It runs what `.github/workflows/ci.yml` runs, with `pnpm@10.30.2` from `packageManager` and `pnpm install --frozen-lockfile`: `pnpm run lint`, `pnpm run typecheck` (`vue-tsc --noEmit`), `pnpm run test:unit --run` and `pnpm run build`, and for a page change `npx vite preview --port 3333` with `cypress run`. It commits any regenerated `src/components.d.ts`, `src/auto-imports.d.ts` or `src/route-map.d.ts`, because `typecheck` on a clean checkout fails without them. Then it opens a pull request whose description links the Linear issue, lists the files by folder and quotes each result. The run does not comment on the Linear issue; your handler posts the link back.

## Start it with one API call

`POST https://api.prix.dev/o/{org}/sessions`

```json
{
  "agent": "codex",
  "input": "Implement the Linear issue given as a resource in this Vue 3 app built with Vite, file-based routes in src/pages, layouts in src/layouts, auto-imported components, composables and a Pinia store, vue-i18n locales in locales/*.yml and static generation through vite-ssg. Read the issue and its comments first. Put the change in the folder the convention names, follow the existing style (script setup with TypeScript, UnoCSS attributes, text through t() with keys in locales/en.yml, a data-test-id on new interactive elements), and guard any window or document access for server rendering. Add the smallest test that proves the acceptance criteria: a Vitest case in test/ with mount from @vue/test-utils and a fresh Pinia, or a case in cypress/e2e/basic.spec.ts for a new page or navigation. Use pnpm 10.30.2 and pnpm install --frozen-lockfile, then run pnpm run lint, pnpm run typecheck, pnpm run test:unit --run and pnpm run build; for a page change also serve npx vite preview --port 3333 and run cypress run. Commit regenerated .d.ts files. Open a pull request whose description links the issue, lists the files by folder and quotes each result. Do not merge.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app"
    },
    {
      "url": "https://linear.app/your-org/issue/ENG-42"
    }
  ]
}
```

1. **Get an API key.** In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
2. **Send the request.** POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
3. **Read what came back.** Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

## What comes back

What comes back is a pull request on its own branch with the Linear issue linked in its description. The diff is grouped by folder: a page or layout, a component with its regenerated entry in `src/components.d.ts`, a store or composable, keys in `locales/en.yml`, and one test in `test/` or `cypress/e2e/`. The description quotes `pnpm run lint`, `pnpm run typecheck`, `pnpm run test:unit --run` and `pnpm run build`, and for a page change the `cypress run` summary with its browser.

A reviewer checks that the folders agree: a new page has its layout block and its `useHead` title, a new string has a key rather than a literal, a new control has a `data-test-id`, and nothing reads `window` during the SSG build. They check the test fails without the change and that the snapshot in `test/__snapshots__/` changed only if a template did. The push runs `ci.yml` on GitHub, so `lint`, `typecheck`, both `test` rows and `test-e2e` confirm what the sandbox ran.

The session holds the issue text the run read and every command. `GET /o/{org}/sessions/{id}` returns the pull request once the turn has finished; your handler reads it there or from the `run.completed` webhook and comments the link on the issue.

## Run it on every Linear issue labelled agent

Create an automation in the Project for this repository with this prompt and a `linear` trigger on `linear.issue.labeled`, with the hand-off label in `config.labels` and your team keys in `config.teams`. Each labelled issue starts one run with the issue as its context on its own branch; a label added by an app integration never fires it. Leave the example issue URL out of the automation's prompt, since the trigger supplies the issue.

For a two-step flow, add a `linear.issue.state_changed` trigger whose `config.states` maps a review state to `plan` and a later state to `exec`. The `plan` run reads the pages, store and locales the issue touches, changes nothing and posts its plan as a comment on the issue; the `exec` run opens the pull request once someone has read it.

## Why run it on Rush?

A small Vue feature in this template touches a page, a locale file, a generated declaration and often a store, and proving it means pnpm 10.30.2, `vue-tsc`, Vitest, an SSG build and a browser running Cypress against `vite preview`. A Rush run does that in its own sandbox with a browser, from a clean clone, and keeps the output of every command, so the pull request can quote the checks instead of promising them.

The change lands in the right folder because the agent reads the conventions in `vite.config.ts`, `src/main.ts` and the existing pages before editing. Several labelled issues run at once, each on its own branch with its own build, and each ends as a linked pull request whose own four CI jobs confirm it, or as a comment on the issue saying what was unclear.

## Frequently asked questions

### How does the run read the Linear issue?

Through the `https://linear.app/<workspace>/issue/<ID>` entry in `resources`, which puts the issue in the prompt through your Linear connection, or through the `linear` trigger, which appends the issue after the prompt. An issue your Linear connection cannot reach answers `422 RESOURCE_NOT_ACCESSIBLE` before a sandbox starts.

### Will it add a new route?

By adding a file under `src/pages/`, which the Vue Router plugin in `vite.config.ts` turns into a route and records in `src/route-map.d.ts`. A dynamic segment is a bracketed filename like `hi/[name].vue`, and the page chooses its layout in a `<route lang="yaml">` block.

### Does new text go straight into the template?

No. Templates render text through `t()` with keys in `locales/en.yml`, loaded by `src/modules/i18n.ts`. The agent adds the key there, and to another locale file only when the issue names that language; the rest fall back.

### Does the run comment the pull request link on the Linear issue?

No. The run reads the issue and the pull request links it, but posting back is your handler's job: read the pull request from the session or the `run.completed` webhook and comment it through your own Linear integration.

### What if the issue asks for something that needs a browser API?

`vite-ssg build` renders every page on the server, so the agent guards `window` and `document` behind `isClient` or `typeof document !== 'undefined'`, as `src/modules/i18n.ts` and `src/modules/pinia.ts` already do, and the build step in CI is what proves it.
