Skip to content

tsk-i6hggr [OPEN] S7a: watch projection endpoint for canvas state - #2204

Open
jaylfc wants to merge 1 commit into
devfrom
exec/tsk-i6hggr
Open

tsk-i6hggr [OPEN] S7a: watch projection endpoint for canvas state#2204
jaylfc wants to merge 1 commit into
devfrom
exec/tsk-i6hggr

Conversation

@jaylfc

@jaylfc jaylfc commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Autonomous build of board card tsk-i6hggr.

REVIEW WARNING (automated): this card's text asks for tests, but the diff changes no test file. Either the acceptance criteria are unmet or the card needs correcting. Do not merge without resolving this.

Files:
tinyagentos/auth_middleware.py | 1 +
tinyagentos/projects/canvas/watch_projection.py | 155 ++++++++++++++++++++++++
tinyagentos/routes/project_canvas.py | 11 ++
3 files changed, 167 insertions(+)


Summary by Gitar

  • New features:
    • Added renderer-neutral watch projection module in watch_projection.py
    • Exposed canvas watch projection endpoint at /api/projects/{project_id}/canvas/watch-projection
  • Security & routing:
    • Whitelisted watch projection endpoint in auth middleware for agent canvas routes

This will update automatically on new commits.

Summary by CodeRabbit

  • New Features
    • Added a canvas watch-projection endpoint for retrieving project canvas content in a renderer-neutral format.
    • Projections include text, image, and link elements, sorted by display order.
    • Unsupported or malformed elements are represented safely with placeholder entries instead of causing failures.
    • Authorized agent requests can access the new canvas endpoint.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a renderer-neutral canvas watch projection builder, exposes it through an authenticated GET endpoint, and permits registry-JWT passthrough for the new route.

Changes

Canvas watch projection

Layer / File(s) Summary
Projection builder
tinyagentos/projects/canvas/watch_projection.py
Projects supported canvas kinds into versioned rows, sorts by z_index, and converts malformed or unsupported elements into placeholders.
Watch projection route
tinyagentos/routes/project_canvas.py
Adds a read-authorized GET endpoint that loads project canvas elements and returns the generated projection.
Registry-JWT route allowlist
tinyagentos/auth_middleware.py
Allows agent registry JWT passthrough for the watch-projection endpoint.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant AuthMiddleware
  participant watch_project_canvas
  participant project_canvas_store
  participant build_watch_projection
  Client->>AuthMiddleware: GET /api/projects/{project_id}/canvas/watch-projection
  AuthMiddleware->>watch_project_canvas: authorize and pass request
  watch_project_canvas->>project_canvas_store: load canvas elements
  project_canvas_store-->>watch_project_canvas: return elements
  watch_project_canvas->>build_watch_projection: build projection
  build_watch_projection-->>Client: return versioned JSON projection
Loading

Possibly related PRs

  • jaylfc/taOS#1825: Expands the canvas-route registry-JWT allowlist in the same middleware.
  • jaylfc/taOS#1838: Changes canvas routing and authentication for agent registry JWT access.
  • jaylfc/taOS#1507: Extends registry-JWT allowlisting to additional project read endpoints.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: adding a watch projection endpoint for canvas state.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch exec/tsk-i6hggr

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.

@gitar-bot

gitar-bot Bot commented Jul 28, 2026

Copy link
Copy Markdown

Important

You are using the Gitar free plan. Upgrade to unlock code review, CI analysis, auto-apply, custom automations, and more.

Gitar

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add renderer-neutral canvas watch projection endpoint

✨ Enhancement 🕐 20-40 Minutes

Grey Divider

AI Description

• Adds an authenticated canvas watch endpoint returning a versioned, renderer-neutral projection.
• Maps canvas elements into watch-friendly cards, thumbnails, links, and defensive placeholders.
• Requested automated coverage is absent, leaving projection and authorization behavior unverified.
Diagram

graph TD
  Client["Watch Client"] --> Auth["Auth Middleware"] --> Route["Projection Route"] --> Store["Canvas Store"] --> Projector["Watch Projector"] --> Response["Versioned JSON"]
Loading
High-Level Assessment

The standalone pure projection module is the appropriate approach: it keeps the endpoint thin, avoids coupling watch consumers to tldraw or Excalidraw, and makes malformed-row handling independently testable. Inline route transformation and renderer-specific models would increase coupling without clear benefit; targeted unit and endpoint tests should be added before merge.

Files changed (3) +167 / -0

Enhancement (2) +166 / -0
watch_projection.pyBuild defensive renderer-neutral canvas projections +155/-0

Build defensive renderer-neutral canvas projections

• Introduces a versioned pure-data projector for text cards, image thumbnails, links, and unsupported-element placeholders. It defensively handles malformed rows and free-form payloads, extracts safe display fields, and orders results by z-index.

tinyagentos/projects/canvas/watch_projection.py

project_canvas.pyExpose the canvas watch projection endpoint +11/-0

Expose the canvas watch projection endpoint

• Adds an authenticated read endpoint that loads project canvas elements and returns the versioned watch projection. It reuses the existing canvas actor authorization flow and store.

tinyagentos/routes/project_canvas.py

Other (1) +1 / -0
auth_middleware.pyAllow agent access to the watch projection route +1/-0

Allow agent access to the watch projection route

• Adds the watch-projection GET path to the agent-accessible canvas route allowlist. Route-level authorization still enforces project binding, canvas scope, and member permissions.

tinyagentos/auth_middleware.py

@jaylfc

jaylfc commented Jul 28, 2026

Copy link
Copy Markdown
Owner Author

nemotron-ultra-orB review

Code Review

Critical Issues

1. project_canvas.py:205 - Auth bypass risk
The route is added to _PUBLIC_ROUTES in auth_middleware.py (bypassing auth middleware) but the handler still calls _authorize_canvas_actor(request, project_id, "read"). If _PUBLIC_ROUTES bypasses auth middleware entirely, the auth check becomes dead code. If it doesn't bypass, the public route registration is misleading. Verify the middleware logic.

2. watch_projection.py:79 - _placeholder_entry can crash on non-dict input

kind = _str_or(el.get("kind"), "unknown") if isinstance(el, dict) else "unknown"
entry = _base_entry(el, kind, "placeholder")  # passes non-dict el to _base_entry

_base_entry handles non-dict el correctly (returns defaults), but _placeholder_entry passes kind="unknown" while _base_entry uses that kind for the kind field — correct. However, if el is a list/string, el.get("kind") would raise AttributeError before the isinstance check because Python evaluates arguments before the ternary. Fix: _str_or(el.get("kind") if isinstance(el, dict) else None, "unknown").

3. project_canvas.py:205 - No pagination / DoS risk
cs.list_elements(project_id) loads all elements unbounded. A project with 100k+ elements will OOM the watch projection. Add limit/offset params and paginate.


Correctness Bugs

4. watch_projection.py:108 - _payload_text returns URL as text

for key in ("text", "title", "label", "source", "url", "caption"):

Keys "source" and "url" are not text content — a link card with {"url": "http://evil.com"} would render the URL as card text. Remove "source", "url" from the fallback keys.

5. watch_projection.py:127 - Redundant or 0 on guaranteed int

projected.sort(key=lambda e: (e.get("z_index") or 0))

_base_entry guarantees z_index is int via _int_or. The or 0 is redundant (though harmless).

6. watch_projection.py:13 - _TEXT_CARD_KINDS tuple missing "heading"
If canvas supports heading elements, they'll fall through to placeholder. Check project_canvas_elements schema for all text-like kinds.


Security Issues

7. watch_projection.py:121 - _domain_of swallows all exceptions

except Exception:
    return ""

Swallowing Exception hides bugs (e.g., urlparse changes). Catch ValueError only, or at least Exception as e: logger.debug(...); return "".


Missing/Weak Test Cases

8. No tests for watch_projection.py — Critical gaps:

  • Empty elements list
  • Elements with payload as list/string/None (not dict)
  • Boolean z_index (tests _int_or bool exclusion)
  • Non-dict elements in list (list, str, int)
  • Malformed payload dicts missing keys
  • Diagram/user_shape kinds → placeholder
  • URL parsing edge cases (no scheme, IPv6, invalid Unicode)

9. No tests for watch_project_canvas route — Missing:

  • Auth failure returns 401/403
  • Pagination params (when added)
  • Large element list performance
  • Project not found / permission denied

Style Issues

10. Missing return type hints_text_card, _thumbnail, _link_row, _placeholder_entry lack -> dict.

11. watch_projection.py:100 - Misleading docstring phrasing

"a list or bare string decodes truthy and would make .get() raise"
"Decodes truthy" is nonsense. Should read: "a list or bare string would cause .get() to raise AttributeError".

12. Inconsistent _base_entry signature — Param entry_type but dict key is "type". Rename param to type_ for consistency.

13. Unused importfrom urllib.parse import urlparse used only in _domain_of. Move import inside function or keep at top (minor).


Summary Priority

Priority Issues
Blocker #1 (auth bypass), #2 (crash on malformed input), #3 (DoS)
Bug #4 (URL as text), #7 (broad except)
Test Gap #8, #9 (no tests for new module/route)
Style #10, #11, #12, #13

Fix #1-3 before merge. Add tests for #8-9.

Automated first-pass review by the nemotron-ultra-orB lane. The lead still reviews before merge.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
tinyagentos/projects/canvas/watch_projection.py (1)

28-43: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add the requested endpoint-contract tests.

No tests accompany this new versioned API, projection behavior, or registry-JWT path.

  • tinyagentos/projects/canvas/watch_projection.py#L28-L43: test supported rows, malformed payload degradation, and deterministic z_index ordering.
  • tinyagentos/routes/project_canvas.py#L201-L208: test authorized success and denied read access.
  • tinyagentos/auth_middleware.py#L87-L87: test exact GET-only JWT passthrough and insufficient-scope/project rejection.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tinyagentos/projects/canvas/watch_projection.py` around lines 28 - 43, Add
endpoint-contract tests covering supported rows, malformed-element placeholder
degradation, and deterministic z_index ordering in
tinyagentos/projects/canvas/watch_projection.py:28-43; authorized success and
denied read access in tinyagentos/routes/project_canvas.py:201-208; and exact
GET-only JWT passthrough plus insufficient-scope/project rejection in
tinyagentos/auth_middleware.py:87-87. Use the existing build_watch_projection
and relevant route/auth symbols, with no production-code changes unless required
to make these contracts testable.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tinyagentos/projects/canvas/watch_projection.py`:
- Around line 134-140: Update _domain_of to parse the URL once and return
parsed.hostname or an empty string, avoiding netloc so userinfo and ports are
excluded; catch the URL parsing error raised for malformed input while
preserving the existing invalid or blank input behavior.

---

Nitpick comments:
In `@tinyagentos/projects/canvas/watch_projection.py`:
- Around line 28-43: Add endpoint-contract tests covering supported rows,
malformed-element placeholder degradation, and deterministic z_index ordering in
tinyagentos/projects/canvas/watch_projection.py:28-43; authorized success and
denied read access in tinyagentos/routes/project_canvas.py:201-208; and exact
GET-only JWT passthrough plus insufficient-scope/project rejection in
tinyagentos/auth_middleware.py:87-87. Use the existing build_watch_projection
and relevant route/auth symbols, with no production-code changes unless required
to make these contracts testable.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 44050b55-44da-4b83-9c70-d33c7b9b8a1c

📥 Commits

Reviewing files that changed from the base of the PR and between 7340c2a and f37e575.

📒 Files selected for processing (3)
  • tinyagentos/auth_middleware.py
  • tinyagentos/projects/canvas/watch_projection.py
  • tinyagentos/routes/project_canvas.py

Comment on lines +134 to +140
def _domain_of(url: Any) -> str:
if not isinstance(url, str) or not url.strip():
return ""
try:
return urlparse(url).netloc or ""
except Exception:
return ""

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Files matching watch_projection.py:\n'
fd -a 'watch_projection\.py$' . || true

file="$(fd 'watch_projection\.py$' . | head -n1 || true)"
if [ -n "${file:-}" ]; then
  printf '\nLine count:\n'
  wc -l "$file"
  printf '\nRelevant source:\n'
  sed -n '1,220p' "$file"
fi

printf '\nSearch for _domain_of usages:\n'
rg -n "_domain_of|watch_projection" .

Repository: jaylfc/taOS

Length of output: 5933


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Behavioral probe for urlsplit/urlparse semantics on userinfo/ports.
python3 - <<'PY'
from urllib.parse import urlsplit, urlparse

urls = [
    "http://example.com/path",
    "http://example.com:8080/path",
    "http://user:pass@example.com/path",
    "http://user@example.com:8080/path",
    "ftp://user:secret@example.com:21/dir/file",
    "",
    "not a url",
]
for url in urls:
    try:
        parsed = urlsplit(url)
    except Exception as e:
        print(f"{url!r} -> urlsplit exception {type(e).__name__} {e}")
        continue
    fields = ({"scheme": parsed.scheme, "netloc": parsed.netloc, "hostname": parsed.hostname, "port": parsed.port})
    print(f"{url!r} -> {fields}")
print("\nUsing urlparse:")
for url in urls:
    try:
        parsed = urlparse(url)
    except Exception as e:
        print(f"{url!r} -> urlparse exception {type(e).__name__} {e}")
        continue
    fields = ({"scheme": parsed.scheme, "netloc": parsed.netloc, "hostname": parsed.hostname, "port": parsed.port})
    print(f"{url!r} -> {fields}")
    if isinstance(url, str) and not url.strip():
        continue
PY

Repository: jaylfc/taOS

Length of output: 1752


Do not expose URL userinfo as the domain.

netloc includes embedded user:pass@, usernames, and ports, which can be stored in the watch projection. Return parsed.hostname instead and catch the URL parsing error that can be raised for malformed input.

🧰 Tools
🪛 Ruff (0.16.0)

[warning] 139-139: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tinyagentos/projects/canvas/watch_projection.py` around lines 134 - 140,
Update _domain_of to parse the URL once and return parsed.hostname or an empty
string, avoiding netloc so userinfo and ports are excluded; catch the URL
parsing error raised for malformed input while preserving the existing invalid
or blank input behavior.

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (4) 📘 Rule violations (0) 📜 Skill insights (2)

Context used
✅ Compliance rules (platform): 35 rules

Grey Divider


Action required

1. Thumbnail source is discarded 🐞 Bug ≡ Correctness
Description
_thumbnail drops payload.file_id and supplies no alternative image source, so every projected
image labeled as a thumbnail is non-renderable. Existing canvas image producers and renderers use
file_id as the image identity.
Code

tinyagentos/projects/canvas/watch_projection.py[R81-83]

+    entry = _base_entry(el, "image", "thumbnail")
+    entry["alt"] = _payload_str(el, "alt")
+    return entry
Relevance

⭐⭐⭐ High

Dropping the sole image identity is a clear local correctness defect.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Image creation stores the only image identity as payload.file_id, while the existing renderer
requires that field to construct its source URL. The new thumbnail projection retains only alt,
making the image content unreachable from the returned entry.

tinyagentos/projects/canvas/mcp_tools.py[109-119]
tinyagentos/tools/project_tools.py[142-172]
desktop/src/apps/ProjectsApp/canvas/shapes/ImageShape.tsx[39-46]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Image projections discard the stored `file_id`, leaving watch clients unable to render the advertised thumbnail.

## Issue Context
Canvas image elements store their image identity in `payload.file_id`, and the existing UI constructs the image endpoint from that identifier. Preserve a validated identifier or provide an authenticated renderer-neutral thumbnail URL.

## Fix Focus Areas
- tinyagentos/projects/canvas/watch_projection.py[80-83]
- tinyagentos/projects/canvas/mcp_tools.py[109-119]
- desktop/src/apps/ProjectsApp/canvas/shapes/ImageShape.tsx[39-46]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. URL credentials exposed as domain 🐞 Bug ⛨ Security
Description
_domain_of returns urlparse(url).netloc, which includes userinfo and ports; for example,
https://alice:******@example.com exposes alice:secret@example.com as the projected domain. This
can leak embedded URL credentials into watch displays, logs, or downstream projection consumers.
Code

tinyagentos/projects/canvas/watch_projection.py[138]

+        return urlparse(url).netloc or ""
Relevance

⭐⭐⭐ High

Credential disclosure has a deterministic hostname-based fix and aligns with accepted URL hardening.

PR-#304

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The unfurl path accepts HTTP(S) URLs by checking parsed.hostname and then stores the original URL
unchanged in payload.url. Because netloc preserves username:password@, the new projection can
serialize credentials into a field intended to contain only the domain.

tinyagentos/projects/canvas/unfurl.py[40-47]
tinyagentos/projects/canvas/unfurl.py[126-132]
tinyagentos/projects/canvas/watch_projection.py[134-140]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The projected `domain` field includes URL userinfo because it is populated from `netloc`.

## Issue Context
Stored link payloads retain the original URL, and URL validation uses `hostname` without rejecting userinfo. Populate `domain` from the parsed hostname instead, applying any desired normalized port handling separately, and add credential-bearing URL coverage.

## Fix Focus Areas
- tinyagentos/projects/canvas/watch_projection.py[134-140]
- tinyagentos/projects/canvas/unfurl.py[40-47]
- tinyagentos/projects/canvas/unfurl.py[126-132]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Projection changes lack tests 🐞 Bug ⚙ Maintainability
Description
The PR adds a new projection contract, route, and bearer-token allowlist entry without any tests,
despite the card’s explicit test acceptance criterion. Element mappings, malformed-payload behavior,
project errors, and authorization boundaries are consequently unverified.
Code

tinyagentos/routes/project_canvas.py[R201-208]

+@router.get("/api/projects/{project_id}/canvas/watch-projection")
+async def watch_project_canvas(project_id: str, request: Request):
+    auth = await _authorize_canvas_actor(request, project_id, "read")
+    if isinstance(auth, JSONResponse):
+        return auth
+    cs = request.app.state.project_canvas_store
+    elements = await cs.list_elements(project_id)
+    return build_watch_projection(elements)
Relevance

⭐⭐⭐ High

The card explicitly requires tests, and the PR warning blocks merging without them.

PR-#260

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The supplied diff modifies no test file and repository search finds no test reference to
watch-projection. Existing tests explicitly enumerate each canvas allowlist route and separately
test endpoint registration and agent read gating, demonstrating where equivalent coverage for this
new route is expected.

tests/test_auth_middleware.py[273-320]
tests/test_routes_project_canvas.py[342-399]
tests/test_routes_project_canvas.py[884-944]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The watch-projection implementation and its authorization exposure have no automated test coverage, contrary to the stated acceptance criterion.

## Issue Context
Add pure projection tests for every supported kind, ordering, malformed/freeform payloads, image sources, and safe domain extraction. Add route and middleware tests covering owner access, agent scope/member flags, wrong-project tokens, nonexistent projects, and exact allowlist matching.

## Fix Focus Areas
- tinyagentos/projects/canvas/watch_projection.py[28-155]
- tests/test_routes_project_canvas.py[342-399]
- tests/test_auth_middleware.py[273-320]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

4. Allowlist documentation not updated 📜 Skill insight § Compliance
Description
The agent-token allowlist now exposes the watch projection endpoint, but the PR does not update
docs/agent-coordination.md or provide the required Docs-Reviewed: declaration. The allowlisted
agent-facing API can therefore drift from its coordination documentation.
Code

tinyagentos/auth_middleware.py[87]

+    ("GET", re.compile(rf"^/api/projects/{_SEG}/canvas/watch-projection$")),
Relevance

⭐⭐⭐ High

Explicit compliance requirement makes documentation or trailer remediation likely.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 2185429 requires an update to docs/agent-coordination.md or a Docs-Reviewed:
commit trailer whenever the allowlist changes. Line 87 adds a new allowlisted route, while the PR's
listed changes contain neither the documentation file nor such a declaration.

tinyagentos/auth_middleware.py[79-88]
Skill: taos-development-skill

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The agent-token route allowlist was changed without the documentation update required by the compliance gate.

## Issue Context
Document the new watch projection route in `docs/agent-coordination.md`. If no user-facing documentation is needed, provide the required `Docs-Reviewed:` commit trailer instead.

## Fix Focus Areas
- tinyagentos/auth_middleware.py[87-87]
- docs/agent-coordination.md[52-79]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

5. Watch projection lacks response model 📜 Skill insight ✧ Quality
Description
The new watch projection route returns a raw dictionary without declaring a Pydantic
response_model. This bypasses the required response-schema validation and documentation.
Code

tinyagentos/routes/project_canvas.py[201]

+@router.get("/api/projects/{project_id}/canvas/watch-projection")
Relevance

⭐ Low

Team recently rejected the same response_model compliance request on raw route dictionaries.

PR-#2122

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 2185155 requires route response payloads to use Pydantic models. The added
decorator has no response_model, while the handler directly returns the dictionary produced by
build_watch_projection.

tinyagentos/routes/project_canvas.py[201-208]
tinyagentos/projects/canvas/watch_projection.py[28-43]
Skill: taos-development-skill

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The watch projection endpoint returns an unvalidated dictionary and does not declare a Pydantic response model.

## Issue Context
Define models for the projection and its element entries, then set the route's `response_model` and return the modeled response.

## Fix Focus Areas
- tinyagentos/routes/project_canvas.py[201-208]
- tinyagentos/projects/canvas/watch_projection.py[28-43]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Missing projects return success 🐞 Bug ≡ Correctness
Description
watch_project_canvas never verifies project existence, so an authorized session request for an
unknown project returns HTTP 200 with an empty projection instead of 404. Clients therefore cannot
distinguish an invalid project from a valid empty canvas.
Code

tinyagentos/routes/project_canvas.py[R206-208]

+    cs = request.app.state.project_canvas_store
+    elements = await cs.list_elements(project_id)
+    return build_watch_projection(elements)
Relevance

⭐ Low

Team recently rejected the same missing-project session-path 404 fix despite older acceptance.

PR-#2122
PR-#260

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The store query returns no rows for an unknown project and therefore produces an empty list. Unlike
the existing PNG route, the new route performs no explicit project lookup; the authorization helper
also permits project is None for session users.

tinyagentos/projects/canvas/store.py[179-205]
tinyagentos/routes/project_canvas.py[125-137]
tinyagentos/routes/project_canvas.py[319-328]
PR-#260

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new watch-projection route returns a successful empty canvas when `project_id` does not exist.

## Issue Context
`list_elements` naturally returns an empty list for an unmatched project ID, and the session authorization path does not reject `project is None`. Follow the existing snapshot route by looking up the project and returning an existence-hiding 404 before building the projection.

## Fix Focus Areas
- tinyagentos/routes/project_canvas.py[201-208]
- tinyagentos/routes/project_canvas.py[319-328]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

("GET", re.compile(rf"^/api/projects/{_SEG}/canvas/snapshot\.png$")),
("GET", re.compile(rf"^/api/projects/{_SEG}/canvas/snapshot\.tldr$")),
("GET", re.compile(rf"^/api/projects/{_SEG}/canvas/stream$")),
("GET", re.compile(rf"^/api/projects/{_SEG}/canvas/watch-projection$")),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

2. Allowlist documentation not updated 📜 Skill insight § Compliance

The agent-token allowlist now exposes the watch projection endpoint, but the PR does not update
docs/agent-coordination.md or provide the required Docs-Reviewed: declaration. The allowlisted
agent-facing API can therefore drift from its coordination documentation.
Agent Prompt
## Issue description
The agent-token route allowlist was changed without the documentation update required by the compliance gate.

## Issue Context
Document the new watch projection route in `docs/agent-coordination.md`. If no user-facing documentation is needed, provide the required `Docs-Reviewed:` commit trailer instead.

## Fix Focus Areas
- tinyagentos/auth_middleware.py[87-87]
- docs/agent-coordination.md[52-79]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +81 to +83
entry = _base_entry(el, "image", "thumbnail")
entry["alt"] = _payload_str(el, "alt")
return entry

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

3. Thumbnail source is discarded 🐞 Bug ≡ Correctness

_thumbnail drops payload.file_id and supplies no alternative image source, so every projected
image labeled as a thumbnail is non-renderable. Existing canvas image producers and renderers use
file_id as the image identity.
Agent Prompt
## Issue description
Image projections discard the stored `file_id`, leaving watch clients unable to render the advertised thumbnail.

## Issue Context
Canvas image elements store their image identity in `payload.file_id`, and the existing UI constructs the image endpoint from that identifier. Preserve a validated identifier or provide an authenticated renderer-neutral thumbnail URL.

## Fix Focus Areas
- tinyagentos/projects/canvas/watch_projection.py[80-83]
- tinyagentos/projects/canvas/mcp_tools.py[109-119]
- desktop/src/apps/ProjectsApp/canvas/shapes/ImageShape.tsx[39-46]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

if not isinstance(url, str) or not url.strip():
return ""
try:
return urlparse(url).netloc or ""

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

5. Url credentials exposed as domain 🐞 Bug ⛨ Security

_domain_of returns urlparse(url).netloc, which includes userinfo and ports; for example,
https://alice:******@example.com exposes alice:secret@example.com as the projected domain. This
can leak embedded URL credentials into watch displays, logs, or downstream projection consumers.
Agent Prompt
## Issue description
The projected `domain` field includes URL userinfo because it is populated from `netloc`.

## Issue Context
Stored link payloads retain the original URL, and URL validation uses `hostname` without rejecting userinfo. Populate `domain` from the parsed hostname instead, applying any desired normalized port handling separately, and add credential-bearing URL coverage.

## Fix Focus Areas
- tinyagentos/projects/canvas/watch_projection.py[134-140]
- tinyagentos/projects/canvas/unfurl.py[40-47]
- tinyagentos/projects/canvas/unfurl.py[126-132]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +201 to +208
@router.get("/api/projects/{project_id}/canvas/watch-projection")
async def watch_project_canvas(project_id: str, request: Request):
auth = await _authorize_canvas_actor(request, project_id, "read")
if isinstance(auth, JSONResponse):
return auth
cs = request.app.state.project_canvas_store
elements = await cs.list_elements(project_id)
return build_watch_projection(elements)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

6. Projection changes lack tests 🐞 Bug ⚙ Maintainability

The PR adds a new projection contract, route, and bearer-token allowlist entry without any tests,
despite the card’s explicit test acceptance criterion. Element mappings, malformed-payload behavior,
project errors, and authorization boundaries are consequently unverified.
Agent Prompt
## Issue description
The watch-projection implementation and its authorization exposure have no automated test coverage, contrary to the stated acceptance criterion.

## Issue Context
Add pure projection tests for every supported kind, ordering, malformed/freeform payloads, image sources, and safe domain extraction. Add route and middleware tests covering owner access, agent scope/member flags, wrong-project tokens, nonexistent projects, and exact allowlist matching.

## Fix Focus Areas
- tinyagentos/projects/canvas/watch_projection.py[28-155]
- tests/test_routes_project_canvas.py[342-399]
- tests/test_auth_middleware.py[273-320]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



def _placeholder_entry(el: dict, label: str) -> dict:
kind = _str_or(el.get("kind"), "unknown") if isinstance(el, dict) else "unknown"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

WARNING: _placeholder_entry crashes on non-dict el

_str_or(el.get("kind"), "unknown") if isinstance(el, dict) else "unknown" evaluates el.get("kind") before the isinstance check. If el is a list/string/int, this raises AttributeError and crashes the entire projection.

Suggested change
kind = _str_or(el.get("kind"), "unknown") if isinstance(el, dict) else "unknown"
kind = _str_or(el.get("kind") if isinstance(el, dict) else None, "unknown")

Reply with @kilocode-bot fix it to have Kilo Code address this issue.

if isinstance(auth, JSONResponse):
return auth
cs = request.app.state.project_canvas_store
elements = await cs.list_elements(project_id)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

WARNING: Missing project existence check returns 200 for nonexistent projects

watch_project_canvas loads elements without verifying project existence. An authorized request for a nonexistent project returns HTTP 200 with an empty projection instead of 404, making it impossible for clients to distinguish an invalid project from an empty canvas. The existing get_canvas_png route checks project existence before proceeding.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Jul 28, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
tinyagentos/projects/canvas/watch_projection.py 101 _placeholder_entry crashes on non-dict el
tinyagentos/routes/project_canvas.py 207 Missing project existence check returns 200 for nonexistent projects
Files Reviewed (3 files)
  • tinyagentos/auth_middleware.py
  • tinyagentos/projects/canvas/watch_projection.py
  • tinyagentos/routes/project_canvas.py

Fix these issues in Kilo Cloud


Reviewed by step-3.7-flash · Input: 131.9K · Output: 29K · Cached: 646.9K

@jaylfc

jaylfc commented Jul 28, 2026

Copy link
Copy Markdown
Owner Author

nemotron-ultra-kilo review

VERDICT: Approved with minor concerns

  • tinyagentos/auth_middleware.py:87 - Added route to public routes list; verify this list doesn't bypass authentication (endpoint in project_canvas.py still calls _authorize_canvas_actor, so likely safe if list is for CORS/rate-limiting only)
  • tinyagentos/projects/canvas/watch_projection.py - Well-structured, defensive pure functions following snapshotter.py precedent; good handling of malformed payloads and bool z_index exclusion
  • tinyagentos/routes/project_canvas.py:200-209 - New endpoint properly authorized; assumes cs.list_elements() returns list[dict] compatible with projection input
  • Missing tests - No test coverage shown for new endpoint or projection logic (should add unit tests for build_watch_projection edge cases and integration test for endpoint)
    VERDICT: Approved with minor concerns
  • tinyagentos/auth_middleware.py:87 - Added route to public routes list; verify this list doesn't bypass authentication (endpoint in project_canvas.py still calls _authorize_canvas_actor, so likely safe if list is for CORS/rate-limiting only)
  • tinyagentos/projects/canvas/watch_projection.py - Well-structured, defensive pure functions following snapshotter.py precedent; good handling of malformed payloads and bool z_index exclusion
  • tinyagentos/routes/project_canvas.py:200-209 - New endpoint properly authorized; assumes cs.list_elements() returns list[dict] compatible with projection input
  • Missing tests - No test coverage shown for new endpoint or projection logic (should add unit tests for build_watch_projection edge cases and integration test for endpoint)

Automated first-pass review by the nemotron-ultra-kilo lane. The lead still reviews before merge.

@jaylfc

jaylfc commented Jul 28, 2026

Copy link
Copy Markdown
Owner Author

Reviewed at code level with empirical probes of the projection module and a full bot adjudication. BLOCKED on two items, both cheap. @hognek fix-forward on this branch; the authoring lane released the card and will not return.

What holds up, do not rework: the projection module's defensive plumbing genuinely works (probed: non-dict rows, list payloads, bare strings, bool z_index all degrade to correct placeholders, sorted by z_index), and the auth wiring is correct end to end: the middleware entry is registry-JWT reachability, not a public bypass; the handler enforces canvas_read + project binding with existence-hiding 404 + the member flag, identical to the sibling canvas routes.

BLOCKING:

  1. Zero tests. The PR body's own banner says do not merge without resolving, and grep confirms nothing anywhere in tests/ touches watch_projection or the route. Add tests/test_watch_projection.py (empty list, each kind mapping, non-dict row, non-dict payload, bool z_index, z ordering) and route tests in tests/test_routes_project_canvas.py (owner 200, agent with canvas_read + flag 200, wrong-project agent token 404, missing-scope 403).
  2. Credential leak, confirmed by running it: watch_projection.py:140 returns urlparse(url).netloc, so a link payload with https://alice:secret@example.com:8080/x projects "alice:secret@example.com:8080" into watch displays. Change to parsed.hostname or "" and add the creds-URL test red-first.

FIX-WITH (small, same commit):
3. _thumbnail (watch_projection.py:80-83) drops payload.file_id, so a thumbnail entry carries no image reference at all; include file_id (or rename the type honestly).
4. docs/agent-coordination.md:199-201 enumerates exactly which routes canvas_read reaches and was not updated for watch-projection; one line.

Bot adjudication so you skip the noise: CodeRabbit's netloc finding and Qodo's no-tests, file_id and docs findings are real (items above). Kilo's _placeholder_entry-crashes-on-non-dict is FALSE, the ternary guards it and I ran the case. Kilo's missing-project-200 inconsistency matches the sibling GET /canvas/elements exactly, pre-existing idiom, ignore here. The nemotron comment claiming an auth bypass via _PUBLIC_ROUTES is FALSE, the list is _AGENT_TASK_ROUTES and both middleware auth and handler authorization still run.

Re-request review after pushing and I will re-verify with the same probes.

@jaylfc

jaylfc commented Jul 29, 2026

Copy link
Copy Markdown
Owner Author

nemotron-super review

VERDICT: No blocking issues found
No blocking issues found

Automated first-pass review by the nemotron-super lane. The lead still reviews before merge.

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.

1 participant