Merge remote-tracking branch 'origin/main'
# Conflicts: # app/androidApp/src/main/kotlin/com/example/aiapp/Sizes.kt
This commit is contained in:
commit
e3e02d55f7
29 files changed
+2737
-479
No files matched your search
@@ -235,6 +235,8 @@ fun SessionScreen(
|
||||
settings: ServerSettings,
|
||||
summary: SessionSummary,
|
||||
onBack: () -> Unit,
|
||||
/** Opens the file explorer on this session's machine, starting where this session works. */
|
||||
onFiles: (FilesTarget) -> Unit,
|
||||
/** What another app shared in while this session is the one open; see [ShareRequest]. */
|
||||
share: ShareRequest? = null,
|
||||
/** Said once [share] has been attached here, so it is not attached again. */
|
||||
@@ -1347,6 +1349,60 @@ fun SessionScreen(
|
||||
// interpolated, so there is nothing for a dropped frame to interrupt), so it is what both
|
||||
// places below fall back to.
|
||||
val imeVisible = WindowInsets.isImeVisible
|
||||
|
||||
// What this session is costing to draw, copied out to somewhere it can be read.
|
||||
//
|
||||
// Written here rather than beside the control that runs it, because everything it measures --
|
||||
// the events, the rows, the units, what the list has on screen, which cards are open -- is this
|
||||
// composable's own state, and a control in a dialog cannot reach it. The control is a row in
|
||||
// [SessionSettingsDialog]: that is where the session's other about-the-session controls are,
|
||||
// and the header is for what a reader presses while reading. It copies rather than opens,
|
||||
// because what it produces is for somewhere else -- a message to whoever is looking at the
|
||||
// code -- and a screenful of timings read on the phone is a screenful nobody can act on.
|
||||
//
|
||||
// Whatever presses this, it is found by its **name**: `ui-trace`'s tap-by-label action resolves
|
||||
// "Session settings" and then "Copy render timings" from what is on screen at that moment, so
|
||||
// `transcript-bench.sh` and `stream-bench.sh` keep working when this moves again. They pressed
|
||||
// it at a coordinate measured once by hand until 2026-09-03, and anything that moved the header
|
||||
// made that tap land on whatever now sat there -- reporting a number that was never measured.
|
||||
val copyRenderReport = {
|
||||
val report =
|
||||
debugReport(
|
||||
device =
|
||||
"device: ${Build.MODEL} (${Build.MANUFACTURER})," +
|
||||
" Android ${Build.VERSION.RELEASE}\n" +
|
||||
// A debuggable build runs Compose at a fraction of release speed, so a
|
||||
// report that did not say which it came from was read as the app's own
|
||||
// cost.
|
||||
"build: ${if (debuggable(context)) "debug" else "release"}",
|
||||
transcript =
|
||||
listOf(
|
||||
" ${items.size} events, ${rows.size} rows, ${units.size} units loaded",
|
||||
" viewport ${listState.layoutInfo.viewportSize.height}px," +
|
||||
" ${listState.layoutInfo.visibleItemsInfo.size} units visible",
|
||||
visibleUnits(units, listState.layoutInfo.visibleItemsInfo, UNITS_START),
|
||||
" ${expandedTools.size} tool calls and ${expandedGroups.size} groups open",
|
||||
),
|
||||
frames = FrameStats.lines(context.refreshHz()),
|
||||
accounting =
|
||||
FrameStats.drawPhase().let { (nanos, count) -> drawAccounting(nanos, count) },
|
||||
crash = lastCrash(context),
|
||||
)
|
||||
context.copyToClipboard("ai-app render report", report)
|
||||
// Also to the log, so a session driving the app over adb can read the same report the
|
||||
// button copies. The clipboard is not reachable from a shell, and a counter nobody can
|
||||
// check from here is a counter that only gets checked by asking Iris to press a button
|
||||
// and paste.
|
||||
Log.i("ai-app", report)
|
||||
// Only once it is somewhere it can be read from, so a copy that never happened does not
|
||||
// throw the stack away with it.
|
||||
clearCrash(context)
|
||||
// Emptied by the copy, so pressing it twice measures two separate stretches of scrolling
|
||||
// rather than one and then the same one again.
|
||||
FrameStats.reset()
|
||||
DebugStats.reset()
|
||||
Toast.makeText(context, "Copied render report", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
Column(Modifier.fillMaxSize()) {
|
||||
Row(
|
||||
@@ -1388,74 +1444,36 @@ fun SessionScreen(
|
||||
// yellow or red near a limit -- and the theme's plain control colour whenever there
|
||||
// is no measurement, since blue is the low end of the scale here and would read as
|
||||
// "checked, and fine" about a machine nobody could reach.
|
||||
// Usage, files, settings -- widest scope first, narrowing to the right, so the
|
||||
// cog stays at the end where every other screen keeps it. Asked for in this order
|
||||
// by Iris on 2026-09-03.
|
||||
Row {
|
||||
// Left of the numbers about the *conversation*, because it is the same kind of
|
||||
// thing about the *app*: what this session is costing to draw. It copies rather
|
||||
// than opens, because what it produces is for somewhere else -- a message to
|
||||
// whoever is looking at the code -- and a screenful of timings read on the
|
||||
// phone
|
||||
// is a screenful nobody can act on.
|
||||
GlyphButton(
|
||||
SPEED_GLYPH,
|
||||
"Copy render timings",
|
||||
onClick = {
|
||||
val report =
|
||||
debugReport(
|
||||
device =
|
||||
"device: ${Build.MODEL} (${Build.MANUFACTURER})," +
|
||||
" Android ${Build.VERSION.RELEASE}\n" +
|
||||
// A debuggable build runs Compose at a fraction of
|
||||
// release speed, so a report that did not say which
|
||||
// it came from was read as the app's own cost.
|
||||
"build: ${if (debuggable(context)) "debug" else "release"}",
|
||||
transcript =
|
||||
listOf(
|
||||
" ${items.size} events, ${rows.size} rows," +
|
||||
" ${units.size} units loaded",
|
||||
" viewport" +
|
||||
" ${listState.layoutInfo.viewportSize.height}px," +
|
||||
" ${listState.layoutInfo.visibleItemsInfo.size}" +
|
||||
" units visible",
|
||||
visibleUnits(
|
||||
units,
|
||||
listState.layoutInfo.visibleItemsInfo,
|
||||
UNITS_START,
|
||||
),
|
||||
" ${expandedTools.size} tool calls and" +
|
||||
" ${expandedGroups.size} groups open",
|
||||
),
|
||||
frames = FrameStats.lines(context.refreshHz()),
|
||||
accounting =
|
||||
FrameStats.drawPhase().let { (nanos, count) ->
|
||||
drawAccounting(nanos, count)
|
||||
},
|
||||
crash = lastCrash(context),
|
||||
)
|
||||
context.copyToClipboard("ai-app render report", report)
|
||||
// Also to the log, so a session driving the app over adb can read the
|
||||
// same report the button copies. The clipboard is not reachable from a
|
||||
// shell, and a counter nobody can check from here is a counter that
|
||||
// only
|
||||
// gets checked by asking Iris to press a button and paste.
|
||||
Log.i("ai-app", report)
|
||||
// Only once it is somewhere it can be read from, so a copy that never
|
||||
// happened does not throw the stack away with it.
|
||||
clearCrash(context)
|
||||
// Emptied by the copy, so pressing it twice measures two separate
|
||||
// stretches
|
||||
// of scrolling rather than one and then the same one again.
|
||||
FrameStats.reset()
|
||||
DebugStats.reset()
|
||||
Toast.makeText(context, "Copied render report", Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
},
|
||||
)
|
||||
GlyphButton(
|
||||
USAGE_GLYPH,
|
||||
"Usage",
|
||||
{ usageOpen = true },
|
||||
colour = usageGlyphColour(usage),
|
||||
)
|
||||
// The machine's files, which is where the answer to "what did it actually
|
||||
// change" is. It opens *over* this screen rather than replacing it -- see
|
||||
// [Screen.Session].
|
||||
GlyphButton(
|
||||
FOLDER_GLYPH,
|
||||
"Files",
|
||||
onClick = {
|
||||
onFiles(
|
||||
FilesTarget(
|
||||
setup = summary.setup,
|
||||
setupName = summary.setupName,
|
||||
// Where this session works, and the machine's own home when it
|
||||
// was never given a directory -- resolved there rather than
|
||||
// guessed at here, since this app does not know that machine's
|
||||
// home and must not invent one.
|
||||
start = summary.cwd?.takeIf { it.isNotBlank() } ?: "~",
|
||||
)
|
||||
)
|
||||
},
|
||||
)
|
||||
// What it opens is about this session, so it sits at the end of the session's
|
||||
// own row. The name is the whole of what it holds today, which is why it is a
|
||||
// cog
|
||||
@@ -2092,6 +2110,7 @@ fun SessionScreen(
|
||||
settingsOpen = false
|
||||
},
|
||||
onDismiss = { settingsOpen = false },
|
||||
onCopyRenderReport = copyRenderReport,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user