Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
31 changes: 21 additions & 10 deletions .agents/skills/writing-kea-logics/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ If your work overlaps it, read the companion skill first.
action that changed the value instead. See
[references/reacting-to-changes.md](references/reacting-to-changes.md).

4. **Generated types are the contract.** Every logic has an auto-generated
`*LogicType.ts` next to it. Import with `import type` and pass it as the kea type
parameter. Never edit the generated file.
4. **Generated types are the contract.** Every logic has an inline generated
`MakeLogicType` block above its `kea()` call. Import a logic type from the logic
source file, never from a separate `*LogicType.ts` file.

5. **Resources that need cleanup go through `cache.disposables`.** See the
[using-kea-disposables](../using-kea-disposables/SKILL.md) skill.
Expand Down Expand Up @@ -131,12 +131,23 @@ today" — they age, so the pattern itself is the source of truth.
## Types and typegen

```ts
import type { fooLogicType } from './fooLogicType'
import { MakeLogicType, kea } from 'kea'

// Generated by kea-typegen. Update if you're an agent, ignore if you're human.
export interface fooLogicValues {
name: string
}

// Generated by kea-typegen. Update if you're an agent, ignore if you're human.
export interface fooLogicActions {
setName: (name: string) => { name: string }
}

export type fooLogicType = MakeLogicType<fooLogicValues, fooLogicActions>
export const fooLogic = kea<fooLogicType>([...])
```

Generated `*LogicType.ts` files are produced by `kea-typegen` (we use the
`3.6.2-leakfix.x` private fork). Commands:
Inline type blocks are produced by `kea-typegen` 3.8.2. Commands:

- `pnpm --filter=@posthog/frontend typegen:watch` — watch mode while writing logics
- `pnpm --filter=@posthog/frontend typegen:write` — one-shot write
Expand All @@ -149,19 +160,19 @@ on a single logic, scope both to that file:

```sh
# Regenerate the type for one logic
pnpm --filter=@posthog/frontend exec kea-typegen write \
-f frontend/src/scenes/foo/fooLogic.ts -r ./frontend/src
pnpm --filter=@posthog/frontend typegen:file frontend/src/scenes/foo/fooLogic.ts

# Type-check just that file (and its imports — fast, but won't catch downstream
# breakage in other files that consume the new types)
pnpm --filter=@posthog/frontend exec tsc --noEmit \
pnpm --filter=@posthog/frontend exec tsgo --noEmit \
frontend/src/scenes/foo/fooLogic.ts
```

Use this loop while writing the logic; run the full `typegen:check` /
`typescript:check` once at the end to confirm nothing else broke.

Never edit a `*LogicType.ts` file by hand — change the logic and re-run typegen.
Do not create or import a separate `*LogicType.ts` file. Change the logic and re-run
typegen so the inline generated block stays authoritative.

For keyed logics, annotate the export explicitly:
`export const fooLogic: LogicWrapper<fooLogicType> = kea<fooLogicType>([...])`.
Expand Down
6 changes: 3 additions & 3 deletions .agents/skills/writing-kea-logics/references/anti-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -343,10 +343,10 @@ That's a `forms` builder waiting to be written.

## Types

### Editing `*LogicType.ts` by hand
### Creating or importing `*LogicType.ts`

It's regenerated by `kea-typegen`. Your changes will be wiped. Change the logic and
re-run typegen.
Logic types are generated inline in the logic source. A separate type file will drift
or be deleted by typegen. Import the type from the logic source and re-run typegen.

### Importing the runtime logic when you only need its type

Expand Down
2 changes: 1 addition & 1 deletion .kearc
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
"rootPath": "./frontend/src",
"typesPath": "./frontend/src",
"writePaths": true,
"inlinePaths": ["./frontend/src/scenes/notebooks"]
"inline": true
}
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@
"jest-environment-jsdom": "^29.3.1",
"jest-image-snapshot": "^6.1.0",
"jest-junit": "^16.0.0",
"kea-typegen": "3.8.2",
"kea-typegen": "3.8.3",
"less": "^3.12.2",
"lint-staged": "catalog:",
"mockdate": "^3.0.5",
Expand Down
80 changes: 77 additions & 3 deletions frontend/src/exporter/ExporterLogin.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import '../scenes/authentication/login/Login.scss'

import clsx from 'clsx'
import { actions, kea, path, reducers, useValues } from 'kea'
import { MakeLogicType, actions, kea, path, reducers, useValues } from 'kea'
import { Form, forms } from 'kea-forms'
import type { DeepPartial, DeepPartialMap, FieldName, ValidationErrorType } from 'kea-forms'

import { BridgePage } from 'lib/components/BridgePage/BridgePage'
import { LemonBanner } from 'lib/lemon-ui/LemonBanner'
Expand All @@ -12,12 +13,85 @@ import { LemonInput } from 'lib/lemon-ui/LemonInput'
import { ERROR_MESSAGES } from 'scenes/authentication/shared/loginErrorMessages'
import { SupportModalButton } from 'scenes/authentication/shared/SupportModalButton'

import type { loginLogicType } from './ExporterLoginType'

export interface LoginForm {
password: string
}

// Generated by kea-typegen. Update if you're an agent, ignore if you're human.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

are the block actually generated and injected in this file, or "generated" in the sense that an agent will write/update them? if the former they should probably be placed in their own type files to prevent conflicts, and if the latter i would remove the "generated" language.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

they're both. They're autogenerated, like the current typegen does, but the expectation is that when an agent does work around this logic, it'll also update the file here. We would still run typegen at the end of it all in CI, but this time it would be more of a "typecheck". The goal/expectation is that 90% of the time when adding a thing or two to a logic, agents will correctly also update the type here (cause there really isn't much to do here for most things), saving a whole extra CI run in the process.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

also, i'd avoid separate files cause this just adds bloat to the codebase. the current generated *Logic.ts files are extremely verbose (duplicate things a lot). we could commit the MakeLogicType stuff to a different file as well, but since all logics are anyway large containers, it doesn't feel like a few extra lines of generated metadata at the top will impact the experience for anyone

export interface loginLogicValues {
generalError: {
code: string
detail: string
} | null
isLoginSubmitting: boolean
isLoginValid: boolean
isSuccess: boolean
login: LoginForm
loginAllErrors: Record<string, any>
loginChanged: boolean
loginErrors: DeepPartialMap<LoginForm, ValidationErrorType>
loginHasErrors: boolean
loginManualErrors: Record<string, any>
loginTouched: boolean
loginTouches: Record<string, boolean>
loginValidationErrors: DeepPartialMap<LoginForm, ValidationErrorType>
showLoginErrors: boolean
}

// Generated by kea-typegen. Update if you're an agent, ignore if you're human.
export interface loginLogicActions {
clearGeneralError: () => {
value: true
}
resetLogin: (values?: LoginForm) => {
values?: LoginForm
}
setGeneralError: (
code: string,
detail: string
) => {
code: string
detail: string
}
setLoginManualErrors: (errors: Record<string, any>) => {
errors: Record<string, any>
}
setLoginValue: (
key: FieldName,
value: any
) => {
name: FieldName
value: any
}
setLoginValues: (values: DeepPartial<LoginForm>) => {
values: DeepPartial<LoginForm>
}
setSuccess: () => {
value: true
}
submitLogin: () => {
value: boolean
}
submitLoginFailure: (
error: Error,
errors: Record<string, any>
) => {
error: Error
errors: Record<string, any>
}
submitLoginRequest: (login: LoginForm) => {
login: LoginForm
}
submitLoginSuccess: (login: LoginForm) => {
login: LoginForm
}
touchLoginField: (key: string) => {
key: string
}
}

export type loginLogicType = MakeLogicType<loginLogicValues, loginLogicActions>

export const loginLogic = kea<loginLogicType>([
path(['exporter', 'ExporterLogin']),
actions({
Expand Down
60 changes: 58 additions & 2 deletions frontend/src/exporter/exporterViewLogic.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,70 @@
import { actions, afterMount, connect, kea, listeners, path, props, reducers, selectors } from 'kea'
import { MakeLogicType, actions, afterMount, connect, kea, listeners, path, props, reducers, selectors } from 'kea'

import { heatmapDataLogic } from 'lib/components/heatmaps/heatmapDataLogic'

import type { exporterViewLogicType } from './exporterViewLogicType'
import type { HrefMatchType } from '../lib/components/heatmaps/heatmapDataLogic'
import type { CommonFilters, HeatmapFilters, HeatmapFixedPositionMode } from '../lib/components/heatmaps/types'
import { ExportType, ExportedData } from './types'

const isUrlPattern = (url: string): boolean => {
return /[*+?^${}()|[\]\\]/.test(url)
}

// Generated by kea-typegen. Update if you're an agent, ignore if you're human.
export interface exporterViewLogicValues {
exportedData: ExportedData
isLoading: boolean
screenshotUrl: string | null
}

// Generated by kea-typegen. Update if you're an agent, ignore if you're human.
export interface exporterViewLogicActions {
setCommonFilters: (filters: CommonFilters) => {
filters: CommonFilters
} // heatmapDataLogic
setHeatmapColorPalette: (palette: string | null) => {
palette: string | null
} // heatmapDataLogic
setHeatmapFilters: (filters: HeatmapFilters) => {
filters: HeatmapFilters
} // heatmapDataLogic
setHeatmapFixedPositionMode: (mode: HeatmapFixedPositionMode) => {
mode: HeatmapFixedPositionMode
} // heatmapDataLogic
setHref: (href: string) => {
href: string
} // heatmapDataLogic
setHrefMatchType: (matchType: HrefMatchType) => {
matchType: HrefMatchType
} // heatmapDataLogic
setWindowWidthOverride: (widthOverride: number | null) => {
widthOverride: number | null
} // heatmapDataLogic
fetchScreenshotUrl: () => {
value: true
}
setIsLoading: (isLoading: boolean) => {
isLoading: boolean
}
setScreenshotUrl: (screenshotUrl: string) => {
screenshotUrl: string
}
}

// Generated by kea-typegen. Update if you're an agent, ignore if you're human.
export interface exporterViewLogicMeta {
__keaTypeGenInternalSelectorTypes: {
exportedData: (arg: ExportedData) => ExportedData
}
}

export type exporterViewLogicType = MakeLogicType<
exporterViewLogicValues,
exporterViewLogicActions,
ExportedData,
exporterViewLogicMeta
>

// This is a simple logic that is mounted by the Exporter view and then can be found by any nested callers
// This simplifies passing props everywhere.
// E.g. api.ts uses this to add the sharing_access_token
Expand Down
7 changes: 4 additions & 3 deletions frontend/src/kea-disposables.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { kea, path } from 'kea'
import { MakeLogicType, kea, path } from 'kea'

import { initKeaTests } from '~/test/init'

import type { logicType } from './kea-disposables.testType'

// Helper: trigger visibilitychange after mutating document.hidden
const setHidden = (hidden: boolean): void => {
Object.defineProperty(document, 'hidden', { configurable: true, get: () => hidden })
Expand All @@ -14,6 +12,9 @@ const setHidden = (hidden: boolean): void => {
document.dispatchEvent(new Event('visibilitychange'))
}

// Generated by kea-typegen. Update if you're an agent, ignore if you're human.
export type logicType = MakeLogicType<{}, {}>

describe('disposablesPlugin', () => {
let logic: ReturnType<typeof kea<logicType>>
let setupCalls: number
Expand Down
Loading
Loading