Skip to content
Merged

0.6.1 #257

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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/e2e-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ jobs:
env:
NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN || github.token }}
SCENARIO_DATABASE_ADMIN_URL: postgresql://postgres:postgres@localhost:5432/postgres
VITE_TENCENT_LBS_JS_KEY: ${{ secrets.VITE_TENCENT_LBS_JS_KEY }}
VITE_WECHAT_ABILITY_MOCKING_ENABLED: "true"
VITE_WECHAT_MINIPROGRAM_WEBVIEW_MOCKING_ENABLED: "false"
WECHAT_ABILITY_MOCKING_ENABLED: "true"
Expand Down
94 changes: 17 additions & 77 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,87 +21,27 @@ PartnerUp helps users find a partner (搭子) effectively and safely.
- Backend: Hono + Drizzle ORM + Postgres-oriented schema / migration workflow
- Frontend: Vue 3 + Vite + TanStack Vue Query + Hono RPC client

## Documentation
## Documentation Routing

Read following documents for the current work when needed and keep them current.

- `docs/00-meta/`: typed input routes, mode SOPs, and framework concepts.
- `docs/00-meta/concepts.md`: load only when boundary language or owner terminology is unclear.
- `docs/00-meta/`: bootstrap workflow, typed input routes, mode SOPs, task packets, search defaults, and promotion rules.
- `docs/10-prd/`: product what/why, user-visible workflows, rules, scope, and business vocabulary.
- `docs/15-alignment/`: load only when MVT is not enough to constrain mutation safely.
- `docs/15-alignment/`: opt-in coordination substrate for risky or reference-sensitive mutation.
- `docs/20-product-tdd/`: cross-unit technical realization and authority boundaries.
- `docs/30-unit-tdd/`: open only when a named hard-unit doc exists and is relevant.
- `docs/30-unit-tdd/`: optional hard-unit technical truth; open only when a named hard-unit doc exists and is relevant.
- `docs/40-deployment/`: runtime, rollout, observability, and recovery truth.
- `tasks/`: agent-owned, task-local workspace for volatile planning, investigation, diagnostics, artifacts, evidence, and collaboration state. Every non-trivial task packet should keep a compact control surface with `Objective & Hypothesis`, `Guardrails Touched`, and `Verification`.
- `apps/backend/AGENTS.md`, `apps/frontend/AGENTS.md`, and nearer `**/AGENTS.md`: local constraints are additive and should be checked before edits in that subtree.

## Operating Model

1. Classify the incoming request as `Intent`, `Constraint`, `Reality`, or `Artifact`.
2. Identify the durable owner and blast radius before choosing how to work.
3. For non-trivial work, open or update a task packet under `tasks/`.
4. Keep the task packet current when discussion, exploration, implementation friction, or verification changes the working state.
5. Choose the active mode for the current slice: `Explore`, `Solidify`, `Execute`, or `Diagnose`.
6. Load only the route doc, mode SOP, and governing anchors needed for that slice.
7. Search source and durable docs with volatile workspaces, generated output, dependencies, caches, and virtual environments excluded by default.
8. Expand into alignment substrate fields only when references, boundaries, state, evidence, or blast radius are still ambiguous.
9. Execute with explicit verification.
10. Re-enter a different mode if evidence or clarity changes.
11. Promote only stable truths after verification.

### Typed Input Guide

- `Intent`: the business wants new behavior, scope, or policy. Update PRD first.
- `Constraint`: product behavior stays the same, but technical, dependency, or environment boundaries changed. Update Product TDD or Unit TDD.
- `Reality`: observed runtime behavior diverges from expectation. Gather evidence first, then fix and add recurrence guards if needed.
- `Artifact`: the requested deliverable is a bounded script, analysis, migration helper, or one-off output. Keep it tactical unless reuse is proven.

### Mode Guide

- `Explore`: map unknowns, alternatives, and assumptions.
- `Solidify`: restate findings into explicit claims, contracts, or decisions.
- `Execute`: implement a clear, verified change.
- `Diagnose`: investigate mismatches between expected and observed reality.

Mode guidance:

- do not assume one task equals one mode
- switch modes when evidence or clarity changes
- mode selection never overrides durable ownership

Task packet guidance:

- task packets are agent-owned and may be updated, split, and reorganized by the agent inside the task boundary
- keep each packet readable, inspectable, and steerable by the human
- preserve a compact control surface with objective, guardrails, verification, current understanding, confirmed constraints, and next step
- split a packet by collaboration pressure rather than by a fixed folder scheme
- keep volatile packet content out of durable docs until it passes the promotion test

Search guidance:

- when searching source or durable docs, exclude `tasks/`, `temp/`, generated output such as `build/` and `dist/`, dependency folders such as `node_modules/`, virtual environments, and tool caches by default
- search those locations only when the task explicitly targets them or when recovering/reviewing task evidence

### Impact Handshake

Before mutating durable truth after alignment expansion, or when blast radius is not obviously local, pause and restate:

- Address and Object: what exact files, anchors, or symbols will change
- State Diff: `From -> To`
- Blast Radius Forecast: what downstream files, modules, or surfaces could be affected
- Invariants Check: what must remain unchanged
- Verification: what concrete proof will bound side effects

If evidence is missing or the durable owner is still unclear, return to `Explore` or `Diagnose` instead of guessing.

### Negotiation Triggers

Pause and ask for human confirmation when:

- the requested change conflicts with an existing product claim or technical contract
- blast radius crosses multiple durable owners and the correct owner is unclear
- a shortcut would damage maintainability, readability, simplicity, or an explicit guardrail
- evidence is insufficient for a bug fix or architectural decision
- `tasks/`: volatile task packets, evidence, drafts, and promotion candidates.
- nearest `AGENTS.md`: additive local constraints before edits in that subtree.

## Work Routing

- For non-trivial work, follow `docs/00-meta/bootstrap-workflow.md` and keep a task packet under `tasks/`.
- Product intent change: `docs/00-meta/input-intent.md` -> `docs/10-prd/`.
- Technical constraint change: `docs/00-meta/input-constraint.md` -> `docs/20-product-tdd/` or `docs/30-unit-tdd/`.
- Runtime mismatch: `docs/00-meta/input-reality.md` -> task packet evidence first.
- Bounded artifact: `docs/00-meta/input-artifact.md` -> task-local artifact unless reuse is proven.
- Risky references, weak evidence, conflict, or non-local blast radius: `docs/15-alignment/README.md`.
- Source and durable-doc searches should exclude `tasks/`, generated output, dependencies, virtual environments, and caches unless explicitly targeted.
- Before subtree edits, read the nearest local `AGENTS.md`.

## Development Workflow

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@
max-width="44rem"
@close="closeRoutePointDrawer"
>
<LocationPickerPanel
<LocationPickerBody
v-if="selectedRoutePoint"
:initial-location="selectedRoutePointInitialLocation"
data-testid="ordering.ride-hailing.route-point.drawer"
Expand Down Expand Up @@ -245,7 +245,7 @@ import {
useOfferListing,
} from "@/domains/commerce/queries/useCommerce";
import type { PickedLocation } from "@/domains/location/model/location-picker";
import LocationPickerPanel from "@/domains/location/ui/LocationPickerPanel.vue";
import LocationPickerBody from "@/domains/location/ui/LocationPickerBody.vue";
import {
applyPickedLocationToRoutePoint,
cloneRoute,
Expand Down
59 changes: 1 addition & 58 deletions apps/frontend/src/domains/location/model/location-picker.test.ts
Original file line number Diff line number Diff line change
@@ -1,64 +1,7 @@
import { describe, expect, test } from "vitest";
import {
buildTencentLocationPickerUrl,
mapTencentLocationPickerPayload,
parsePickedLocation,
serializePickedLocation,
} from "./location-picker";
import { parsePickedLocation, serializePickedLocation } from "./location-picker";

describe("location picker helpers", () => {
test("buildTencentLocationPickerUrl creates iframe URL with locpicker params", () => {
const url = new URL(
buildTencentLocationPickerUrl({
key: "test-key",
referer: "partner-up-test",
initialCoordinate: [23.10647, 113.32446],
}),
);

expect(url.origin).toBe("https://apis.map.qq.com");
expect(url.pathname).toBe("/tools/locpicker");
expect(url.searchParams.get("type")).toBe("1");
expect(url.searchParams.get("search")).toBe("1");
expect(url.searchParams.get("mapdraggable")).toBe("1");
expect(url.searchParams.get("coord")).toBeNull();
expect(url.searchParams.get("coordtype")).toBeNull();
});

test("buildTencentLocationPickerUrl leaves initial coordinate in the local draft only", () => {
expect(
buildTencentLocationPickerUrl({
key: "test-key",
referer: "partner-up-test",
initialCoordinate: [23.12908, 113.26436],
}),
).toBe(
"https://apis.map.qq.com/tools/locpicker?type=1&search=1&mapdraggable=1&key=test-key&referer=partner-up-test",
);
});

test("mapTencentLocationPickerPayload maps Tencent callback into PickedLocation", () => {
expect(
mapTencentLocationPickerPayload({
module: "locationPicker",
latlng: {
lat: 39.998766,
lng: 116.273938,
},
poiaddress: "北京市海淀区新建宫门路19号",
poiname: "颐和园",
cityname: "北京市",
}),
).toEqual({
name: "颐和园",
address: "北京市海淀区新建宫门路19号",
cityName: "北京市",
gcj02: [39.998766, 116.273938],
});

expect(mapTencentLocationPickerPayload({ module: "other" })).toBeNull();
});

test("picked location serialization keeps a validated payload", () => {
const serialized = serializePickedLocation({
name: "广州塔",
Expand Down
69 changes: 0 additions & 69 deletions apps/frontend/src/domains/location/model/location-picker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,6 @@ export type PickedLocation = {
gcj02: RouteCoordinate;
};

export type TencentLocationPickerUrlInput = {
key: string;
referer: string;
initialCoordinate?: RouteCoordinate | null;
search?: boolean;
mapDraggable?: boolean;
};

type TencentLocationPickerLatLng = {
lat: unknown;
lng: unknown;
};

type TencentLocationPickerPayload = {
module?: unknown;
latlng?: TencentLocationPickerLatLng;
poiname?: unknown;
poiaddress?: unknown;
cityname?: unknown;
};

const LOCATION_PICKER_ORIGIN = "https://apis.map.qq.com";
const LOCATION_PICKER_PATH = "/tools/locpicker";

const normalizeNullableText = (value: unknown): string | null => {
if (typeof value !== "string") {
return null;
Expand All @@ -57,51 +33,6 @@ export const clonePickedLocation = (
}
: null;

export const buildTencentLocationPickerUrl = ({
key,
referer,
search = true,
mapDraggable = true,
}: TencentLocationPickerUrlInput): string => {
const url = new URL(LOCATION_PICKER_PATH, LOCATION_PICKER_ORIGIN);
url.searchParams.set("type", "1");
url.searchParams.set("search", search ? "1" : "0");
url.searchParams.set("mapdraggable", mapDraggable ? "1" : "0");
url.searchParams.set("key", key);
url.searchParams.set("referer", referer);
return url.toString();
};

export const mapTencentLocationPickerPayload = (
payload: unknown,
): PickedLocation | null => {
if (!isRecord(payload)) {
return null;
}

const maybePayload = payload as TencentLocationPickerPayload;
if (maybePayload.module !== "locationPicker") {
return null;
}

const latlng = maybePayload.latlng;
if (!latlng || !isFiniteNumber(latlng.lat) || !isFiniteNumber(latlng.lng)) {
return null;
}

const name = normalizeNullableText(maybePayload.poiname);
if (!name) {
return null;
}

return {
name,
address: normalizeNullableText(maybePayload.poiaddress),
cityName: normalizeNullableText(maybePayload.cityname),
gcj02: [latlng.lat, latlng.lng],
};
};

export const serializePickedLocation = (location: PickedLocation): string =>
JSON.stringify(location);

Expand Down
3 changes: 2 additions & 1 deletion apps/frontend/src/domains/location/ui/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ This folder owns generic location picking UI.

## Component Contracts

- `LocationPickerPanel.vue`: Tencent `componentPicker`-backed location picker that returns `PickedLocation`; consuming domains map the picked location into their own payload shape.
- `LocationPickerBody.vue`: Tencent Map JavaScript GL SDK-backed location picker body that returns `PickedLocation`; consuming domains map the picked location into their own payload shape.
- `LocationPickerPanel.vue`: compatibility wrapper only; do not add picker business logic here.

## Boundaries

Expand Down
Loading