## Implementation Plan: Add Nullable Annotations to Custom /docs API Definition
### 1. Modified Files & Changes
| File | Change |
|---|---|
| `devplacepy/docs_api/_shared.py` | Add `nullable: bool = False` parameter to `field()`. Store it in the field metadata dictionary. |
| `devplacepy/docs_examples.py` | Update `schema_example()`: after generating a sample value via `_unwrap_optional()`, if the field has `nullable=True` and the generated value is not already `None`, replace it with `None` (JSON `null`). Also append `" (nullable)"` to the field’s label or description in the generated example dict. |
| `devplacepy/routers/profile.py` (or wherever `ProfileOut` endpoint is defined) | In the `field()` call for the `projects` list member (or the relevant response field), add `nullable=True` for each Optional field that is currently nullable. For example: `field(name="updated_at", type=str, nullable=True)`. If the router uses a shared schema definition, add annotations there. |
| `static/js/ApiTester.js` (approximate path: `devplacepy/static/` or `docs_api/static/`) | In `renderExpected()` (line ~318), check if the field object has a `nullable` property. If true, append a small badge or label like `[nullable]` next to the field name in the JSON sample (e.g., as a comment or in a separate column). Alternative: render the sample value as `null` with a tooltip. |
| New test file: `tests/unit/test_docs_nullable.py` | Write unit tests that:
– Verify `field(nullable=True)` sets the metadata correctly.
– Verify `schema_example()` produces `null` for a nullable field.
– Verify the rendered JSON snippet in `ApiTester.js` includes a nullability indicator (requires a Playwright screenshot or DOM assertion – optional if unit tests for Python logic are sufficient). |
### 2. Detailed Steps (for a competent engineer)
**Step A** – In `devplacepy/docs_api/_shared.py`:
- Add `nullable: bool = False` to the function signature of `field()`.
- Inside `field()`, add a key `"nullable"` to the returned dict (or to the field metadata) set to the value of `nullable`.
**Step B** – In `devplacepy/docs_examples.py`:
- Locate the `_unwrap_optional()` call (line ~45). After resolving the final type and generating a default sample, check if the field’s metadata dict has `"nullable": True`.
- If so, replace the generated sample value with `None` (JSON `null`) in the output dict.
- Also modify the field’s label string (if any) to append `" (nullable)"`.
**Step C** – Update router definitions:
- Find all endpoints that use `ProjectOut` or `ProfileOut` and whose `field()` calls currently lack `nullable=True` for Optional fields.
- Add `nullable=True` to `field(name="updated_at", ...)`.
- Repeat for other fields that are `Optional[...]` in the Pydantic schemas but are only referenced through the `field()` function for /docs (the router might not use `field()` at all – if that’s the case, skip and the sample generator will infer nullability from the Pydantic model if the model is used directly. The ticket’s custom docs may not use Pydantic models directly; it uses `field()` calls. We must assume that each endpoint manually lists fields via `field()` – verify in the code).
**Step D** – Modify `ApiTester.js`:
- In the `renderExpected()` method, iterate over the fields of the example response (the sample object). If a field metadata has `nullable: true`, add a small attribute or CSS class to the rendered `` element, or insert a `nullable` after the field name for human readers.
**Step E** – Write unit tests:
- `test_field_nullable_parameter()` calls `field(nullable=True)` and checks returned dict.
- `test_schema_example_nullable_field()` creates a mock schema with a nullable field and calls `schema_example()`, asserts `None` appears in output.
- (Optional) `test_api_tester_renders_nullable()` uses Playwright to open the /docs page for a known endpoint and checks that the nullable badge is present.
### 3. Definition of Done
- [ ] The `field()` function in `devplacepy/docs_api/_shared.py` accepts `nullable: bool = False` and stores it.
- [ ] The `schema_example()` function in `devplacepy/docs_examples.py` returns `null` in JSON for any field marked `nullable=True`.
- [ ] All endpoints that return `ProjectOut` or `ProfileOut` have their `field()` definitions updated to pass `nullable=True` on fields like `updated_at`.
- [ ] The custom /docs page displays a visual indicator (badge or label) next to nullable fields in the sample response.
- [ ] All existing unit tests pass.
- [ ] The project’s verification command succeeds:
```
pip install -e '.[dev]' -q && python -m pytest tests/unit/test_docs_nullable.py tests/unit/test_docs_examples.py tests/unit/test_shared.py
```
(or the minimal set of test files that cover the changed modules; if no such files exist, create them as part of the plan and ensure they pass.)
- [ ] No regression in the previously failed e2e tests (those tests failed due to Playwright environment issues unrelated to this change; the new code must not introduce additional Playwright failures – run `pytest tests/e2e/` in a compatible environment to confirm).