Skip to content

Adfa 3802 make documentation bookshelf dynamic#1353

Open
jimturner-adfa wants to merge 5 commits into
stagefrom
ADFA-3802-Make-documentation-bookshelf-dynamic
Open

Adfa 3802 make documentation bookshelf dynamic#1353
jimturner-adfa wants to merge 5 commits into
stagefrom
ADFA-3802-Make-documentation-bookshelf-dynamic

Conversation

@jimturner-adfa
Copy link
Copy Markdown
Collaborator

First working version of dynamic bookshelf from curl
Need to add HTML template

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 29, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fbab51a2-b136-4df6-8683-3fb00ae18b5f

📥 Commits

Reviewing files that changed from the base of the PR and between ccfea34 and b32a684.

📒 Files selected for processing (1)
  • app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt

📝 Walkthrough
  • New Features

    • Dynamic Documentation Bookshelf Endpoint (/pr/bs)
      • Adds /pr/bs endpoint that queries the database to build a JSON payload of bookshelf categories and books, instantiates a Pebble template named "bookshelf", and returns rendered HTML.
      • Bookshelf template ID is lazy-loaded on first use and cached in-memory.
      • Two-stage handler implemented: handleBsEndpoint (wrapper with error gating) and realHandleBsEndpoint (core query + render logic).
  • Technical Changes

    • Template instantiation improvements
      • Adds pebbleEngine and templateCache (ConcurrentHashMap) for compiled template caching.
      • instantiatePebbleTemplate now caches compiled templates, reads template blobs from DB, parses JSON context with Jackson, evaluates templates and returns bytes.
      • Extra debug logging added: logs raw template blob content and full parsed template context.
    • Error handling changes
      • handleBsEndpoint now captures a boolean return (outputStarted) from realHandleBsEndpoint and uses it to decide whether to write a 500 error response if an exception occurs.
      • sendError gains an outputStarted parameter to avoid writing an error after output has begun.
    • Misc
      • New imports: Pebble engine classes, Jackson ObjectMapper, ConcurrentHashMap.
      • Adds bookshelf-specific SQL that constructs JSON from Bookshelf/BookCategories/Content tables.
  • Risks & Best-Practice Violations / Notes

    • Incomplete artifact: The PR notes an HTML template still needs to be added; feature is partial until the actual bookshelf template content is present.
    • Thread-safety: bookshelfTemplateId is a mutable Int (-1 sentinel) updated without synchronization; concurrent requests can race causing multiple DB lookups or inconsistent state.
    • Template/context sensitive logging: Debug logs now print raw template blobs and the full template context (map) which can expose sensitive or large data when debug logging is enabled.
    • Response-state race: outputStarted is determined from realHandleBsEndpoint's return value only after that method completes. If realHandleBsEndpoint begins writing (e.g., via writeNormalToClient) and then throws mid-write, the wrapper may still consider outputStarted=false and attempt to send a second response; Code comments acknowledge this known issue.
    • Null / cursor handling: Several database paths return early via sendError but rely on callers to respect output state; cursor reuse in realHandleBsEndpoint (reassigning cursor) increases risk of accidental misuse—careful cursor.close() sequencing is required.
    • Potential logging verbosity/performance: Logging large template/context contents may impact performance or flood logs in high-load scenarios.
  • Testing / Validation Recommendations

    • Add unit/integration tests for concurrent /pr/bs requests to validate template id caching and avoid races.
    • Ensure the bookshelf Pebble template is added and validated for expected context keys.
    • Sanitize or remove debug logging of template blobs and context before enabling debug in non-development environments.

Walkthrough

Adds a high-priority /pr/bs endpoint that queries bookshelf data, lazily caches a Pebble template ID, renders the response via instantiatePebbleTemplate with extra debug logs, and writes output with error responses gated on whether output started.

Changes

Bookshelf Endpoint Feature

Layer / File(s) Summary
Bookshelf endpoint setup
app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt
Adds Okio import changes, a cached bookshelfTemplateId field, expands handleClient KDoc, and routes "pr/bs" to handleBsEndpoint.
Bookshelf endpoint implementation
app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt
Adds handleBsEndpoint wrapper that tracks outputStarted, documents handlePrEndpoint KDoc, and implements realHandleBsEndpoint which queries the DB for bookshelf data, validates the cursor row, lazily resolves/caches the bookshelf template ID, renders via instantiatePebbleTemplate, writes the response with writeNormalToClient, and returns a boolean success flag.
Template rendering debug logging
app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt
Adds/updates KDoc for instantiatePebbleTemplate, logs the fetched templateBlob content, and logs the computed template context map before evaluating the Pebble template.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • davidschachterADFA
  • hal-eisen-adfa

Poem

🐇 I hopped through routes and cached a key,
Pebble dreams turned data to tea,
Bookshelf JSON danced in debug light,
Output guarded, errors kept polite,
A rabbit cheers for templates done right!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: making the documentation bookshelf dynamic, which aligns with the core functionality added in the WebServer.kt changes.
Description check ✅ Passed The description is directly related to the changeset, noting it's the first working version of dynamic bookshelf and mentions the HTML template requirement, which matches the PR objectives.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 ADFA-3802-Make-documentation-bookshelf-dynamic
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch ADFA-3802-Make-documentation-bookshelf-dynamic

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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 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 `@app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt`:
- Around line 696-708: The code in WebServer.kt overwrites the existing cursor
when running the template-ID query (affecting bookshelfTemplateId) and never
closes the original cursor; fix by ensuring each Cursor is closed — either use a
second local variable for the template query (e.g., templateCursor) or wrap each
rawQuery result in a Kotlin use { } block so cursor.close() is called
automatically before assigning or returning; update the block around the
rawQuery/cursor.moveToFirst()/getInt(0) logic that sets bookshelfTemplateId and
any sendError branches to close the appropriate cursor(s).
- Around line 67-69: When the DB handle is reopened/rotated (the block that
reassigns the database variable), clear the template caches so old templates
from the previous DB aren’t reused: call templateCache.clear() and reset
bookshelfTemplateId = -1 immediately after the database swap (the same place
where the code reopens/assigns database). This ensures PebbleTemplate entries in
templateCache and the cached bookshelfTemplateId are invalidated when the
database changes.
- Around line 567-578: The catch block can append a second 500 because
outputStarted is only set true after realHandleBsEndpoint returns; modify the
flow so outputStarted is set to true as soon as any headers/body are written
(i.e., inside the write path) rather than only on return. Concretely, update
realHandleBsEndpoint (or the helper that calls writeNormalToClient) to accept a
mutable flag (e.g., AtomicBoolean or a setter lambda) or return a result object
so it flips outputStarted to true immediately when the first write/flush
happens, then keep the existing try/catch in handleBsEndpoint to rely on that
flag. Ensure references: handleBsEndpoint, realHandleBsEndpoint, and the write
method (writeNormalToClient) are changed accordingly so the catch sees the
correct outputStarted state.
- Around line 713-721: The catch block that logs the fetch error and calls
sendError must stop further processing so instantiatePebbleTemplate(...) is not
called with uninitialized jsonText or bookshelfTemplateId; modify the catch
(Exception e) handling around the fetch to, after logging and calling
sendError(writer, output, 500, ...), immediately return (or otherwise
short-circuit) from the surrounding method so cursor.close() remains in finally
and instantiatePebbleTemplate(...) is never reached when an exception occurred.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7f80d995-fb24-456b-a5b1-4809b7f61d43

📥 Commits

Reviewing files that changed from the base of the PR and between 7defc84 and ccfea34.

📒 Files selected for processing (1)
  • app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt

Comment on lines +67 to +69
private val pebbleEngine = PebbleEngine.Builder().loader(StringLoader()).build()
private val templateCache = ConcurrentHashMap<Int, PebbleTemplate>()
private var bookshelfTemplateId : Int = -1;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Invalidate these caches when the database handle changes.

Lines 308-313 can reopen database, but templateCache and bookshelfTemplateId survive that swap. After a reload, /pr/bs can keep using a template ID or compiled template from the previous database.

🤖 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 `@app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt` around
lines 67 - 69, When the DB handle is reopened/rotated (the block that reassigns
the database variable), clear the template caches so old templates from the
previous DB aren’t reused: call templateCache.clear() and reset
bookshelfTemplateId = -1 immediately after the database swap (the same place
where the code reopens/assigns database). This ensures PebbleTemplate entries in
templateCache and the cached bookshelfTemplateId are invalidated when the
database changes.

Comment on lines +567 to +578
private fun handleBsEndpoint(writer: PrintWriter, output: java.io.OutputStream) {
if (debugEnabled) log.debug("Entering handleBsEndpoint().")

var outputStarted = false

try {

outputStarted = realHandleBsEndpoint(writer, output)

} catch (e: Exception) {
log.error("Error handling /pr/bs endpoint: {}", e.message)
sendError(writer, output, 500, "Internal Server Error 6", "Error generating bookshelf HTML.", outputStarted)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Set outputStarted before the first write.

outputStarted only becomes true after realHandleBsEndpoint() returns. If writeNormalToClient() throws after sending headers or part of the body, this catch will still append a second 500 because the flag is still false.

🤖 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 `@app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt` around
lines 567 - 578, The catch block can append a second 500 because outputStarted
is only set true after realHandleBsEndpoint returns; modify the flow so
outputStarted is set to true as soon as any headers/body are written (i.e.,
inside the write path) rather than only on return. Concretely, update
realHandleBsEndpoint (or the helper that calls writeNormalToClient) to accept a
mutable flag (e.g., AtomicBoolean or a setter lambda) or return a result object
so it flips outputStarted to true immediately when the first write/flush
happens, then keep the existing try/catch in handleBsEndpoint to rely on that
flag. Ensure references: handleBsEndpoint, realHandleBsEndpoint, and the write
method (writeNormalToClient) are changed accordingly so the catch sees the
correct outputStarted state.

Comment on lines +696 to +708
if (bookshelfTemplateId == -1) {
cursor = database.rawQuery("SELECT id FROM Templates WHERE name = 'bookshelf'", arrayOf())

if (cursor.count != 1) {
if (cursor.count == 0)
sendError(writer, output, 404, "Not Found")
else
sendError(writer, output, 500, "Corrupt database - ${cursor.count} Bookshelf templates found when 1 was expected.")
return false
}

cursor.moveToFirst()
bookshelfTemplateId = cursor.getInt(0);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Don't overwrite the first cursor before closing it.

The cursor from rawQuery(sql_query, ...) is replaced by the template-ID query, so the bookshelf-result cursor never gets closed on the initial /pr/bs request. Use a second local cursor or nested use {} blocks here.

🤖 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 `@app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt` around
lines 696 - 708, The code in WebServer.kt overwrites the existing cursor when
running the template-ID query (affecting bookshelfTemplateId) and never closes
the original cursor; fix by ensuring each Cursor is closed — either use a second
local variable for the template query (e.g., templateCursor) or wrap each
rawQuery result in a Kotlin use { } block so cursor.close() is called
automatically before assigning or returning; update the block around the
rawQuery/cursor.moveToFirst()/getInt(0) logic that sets bookshelfTemplateId and
any sendError branches to close the appropriate cursor(s).

Comment on lines +713 to +721
} catch (e: Exception) {
log.error("Error processing request: {}", e.message)
sendError(writer, output, 500, "Internal Server Error", e.message ?: "")

} finally {
cursor.close()
}

val result = instantiatePebbleTemplate(bookshelfTemplateId, jsonText, "/bookshelf", "application/json", "none")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Return immediately after sending the fetch-time error.

If anything in the fetch block fails before jsonText or bookshelfTemplateId is initialized, this catch writes an error and then continues into instantiatePebbleTemplate(...). That turns one failure into a second exception and can trigger another response write.

Suggested fix
         } catch (e: Exception) {
             log.error("Error processing request: {}", e.message)
             sendError(writer, output, 500, "Internal Server Error", e.message ?: "")
+            return false
 
         } finally {
             cursor.close()
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} catch (e: Exception) {
log.error("Error processing request: {}", e.message)
sendError(writer, output, 500, "Internal Server Error", e.message ?: "")
} finally {
cursor.close()
}
val result = instantiatePebbleTemplate(bookshelfTemplateId, jsonText, "/bookshelf", "application/json", "none")
} catch (e: Exception) {
log.error("Error processing request: {}", e.message)
sendError(writer, output, 500, "Internal Server Error", e.message ?: "")
return false
} finally {
cursor.close()
}
val result = instantiatePebbleTemplate(bookshelfTemplateId, jsonText, "/bookshelf", "application/json", "none")
🤖 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 `@app/src/main/java/com/itsaky/androidide/localWebServer/WebServer.kt` around
lines 713 - 721, The catch block that logs the fetch error and calls sendError
must stop further processing so instantiatePebbleTemplate(...) is not called
with uninitialized jsonText or bookshelfTemplateId; modify the catch (Exception
e) handling around the fetch to, after logging and calling sendError(writer,
output, 500, ...), immediately return (or otherwise short-circuit) from the
surrounding method so cursor.close() remains in finally and
instantiatePebbleTemplate(...) is never reached when an exception occurred.

if (debugEnabled) log.debug("before fetch bookshelf template ID = '${bookshelfTemplateId}'")

//Have we already fetched the template
if (bookshelfTemplateId == -1) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Could you avoid the nested structure?

if () {
  if () {
    if () {

    }
  }
}


} finally {
projectDatabase?.close()
sendError(writer, output, 500, "Internal Server Error 6", "Error generating bookshelf HTML.", outputStarted)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

You could create a constant to centralize the 500 value.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 1, 2026

Note

Docstrings generation - SUCCESS
Generated docstrings and committed to branch ADFA-3802-Make-documentation-bookshelf-dynamic (commit: b32a684c2cbd44fcf8ac0fabab5e4d412aedf88a)

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.

3 participants