Skip to content
Merged
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
31 changes: 27 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ techniques:
| `loki-spreadsheet/src/routes/editor/editor_inner.rs` | 1241 | High |
| `loki-ooxml/src/docx/write/document.rs` | 1169 | High |
| `loki-ooxml/src/docx/reader/document.rs` | 1126 | High |
| `loki-text/src/routes/editor/editor_inner.rs` | 968 | High |
| `loki-text/src/routes/editor/editor_inner.rs` | 1040 | High |
| … 24 more (300–600 lines) — see the audit (10 files split 2026-06-21) | | |

(`odt/mapper/document.rs` (1094 lines) was split into the `odt/mapper/document/`
Expand Down Expand Up @@ -203,6 +203,16 @@ The workspace is a set of focused crates (one responsibility each). Key groups:
- **Layout & rendering:** `loki-layout` (renderer-agnostic, Parley-based),
`loki-vello` / `loki-renderer` / `loki-render-cache` (GPU paint; per-page
tiles bounded by viewport virtualization).
- **Spell check:** `loki-spell` — Hunspell-compatible spell checking via the
pure-Rust `spellbook` engine (no FFI). Tokenises text into checkable words,
returns misspelled byte ranges + ranked suggestions; bundles a permissive
`en` dictionary and a license-gated download catalog for other languages.
`loki-layout` injects a checker via `LayoutOptions::spell` and emits
`DecorationKind::Spelling` squiggles (painted by `loki-vello`). The shared
runtime — `loki_app_shell::spell::SpellService` (locale detection, dictionary
cache, `reqwest` fetcher) — is provided into all three apps' context;
`loki-text` renders squiggles end-to-end via `loki_renderer::spell` ambient
state. See §11 of `docs/fidelity-status.md` for what is wired vs. pending.
- **UI & apps:** `appthere-ui` (shared design system), `appthere-canvas`,
`loki-i18n`, `loki-fonts`, and the binaries `loki-text` (word processor —
the mature app), `loki-spreadsheet`, `loki-presentation`.
Expand Down Expand Up @@ -277,6 +287,20 @@ These work in production code:
- `overflow-x: auto`, `overflow-y: auto`
- `border-radius: Npx`
- `position: relative`, `z-index: N`
- `position: absolute` (block-level) — **confirmed working** (2026-06-28). A
block element with `position: absolute` + `top/left/right/bottom` insets,
child of a `position: relative` parent, lays out out-of-flow at the resolved
position, paints above in-flow siblings (and above the wgpu canvas), and
hit-tests correctly. Verified with a runtime probe (red top/left box + green
bottom/right box both anchored correctly; in-flow sibling not displaced). The
floating spelling context menu (`editor_spell_panel`) relies on this. The
earlier "unsupported" claim predated the Stylo + stylo_taffy 0.2 + Taffy 0.9
stack and was stale. **Caveats (still unverified / known-incomplete):**
absolute inside an *inline* formatting context (`blitz-dom`
`layout/inline.rs` has a `TODO: Implement absolute positioning`); the
containing block is the *immediate* positioned parent only (blitz-dom does
not walk up to a non-immediate positioned ancestor); and `overflow: hidden`
on the containing block clips the out-of-flow child.
- `height: calc(100vh - Npx)`, `width: 100vw`, `height: 100vh`
- `border-bottom/top: Npx solid COLOR`
- `box-sizing: border-box`
Expand All @@ -288,9 +312,8 @@ These work in production code:
- `text-overflow: ellipsis` (needed for tab label truncation)
- `overflow-x: scroll` (explicit scroll vs. auto)
- `scrollbar-width: none` (needed for invisible scroll containers)
- `position: absolute` — **confirmed unsupported** in current Blitz.
Tooltip components are deferred until this is resolved.
- `position: fixed` — **confirmed unsupported** (documented in toolbar.rs).
- `position: fixed` — collapses to `absolute` in `stylo_taffy` (not truly
viewport-fixed); use `position: absolute` in a positioned ancestor instead.

### Token usage

Expand Down
28 changes: 28 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[workspace]
resolver = "3"
members = ["loki-doc-model","loki-sheet-model","loki-opc","loki-primitives","loki-ooxml","loki-odf","loki-layout","loki-vello","appthere-ui","loki-app-shell","loki-text","loki-spreadsheet","loki-presentation","loki-render-cache","loki-renderer","loki-i18n","appthere-canvas","loki-fonts","loki-graphics","loki-presentation-model","loki-epub","loki-pdf","loki-acid","loki-templates"]
members = ["loki-doc-model","loki-sheet-model","loki-opc","loki-primitives","loki-ooxml","loki-odf","loki-layout","loki-vello","appthere-ui","loki-app-shell","loki-text","loki-spreadsheet","loki-presentation","loki-render-cache","loki-renderer","loki-i18n","appthere-canvas","loki-fonts","loki-graphics","loki-presentation-model","loki-epub","loki-pdf","loki-acid","loki-templates","loki-spell"]

[workspace.dependencies]
# Shared across appthere-ui and any other workspace member that needs Dioxus.
Expand Down
16 changes: 16 additions & 0 deletions android/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# SPDX-License-Identifier: Apache-2.0
# Build outputs
.gradle/
build/
app/build/

# Staged by scripts/build-aab.sh from the canonical sources — not committed:
# * native libraries built by cargo
# * FilePickerActivity.java copied from patches/loki-file-access/android/
app/src/main/jniLibs/
app/src/main/java/

# Local signing config
keystore.properties
*.keystore
!/*.placeholder
65 changes: 65 additions & 0 deletions android/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// SPDX-License-Identifier: Apache-2.0
plugins {
// Pure-native app: the only Java is FilePickerActivity, so no Kotlin plugin.
id("com.android.application")
}

android {
namespace = "com.appthere.loki"
compileSdk = 34

defaultConfig {
applicationId = "com.appthere.loki"
// Vulkan (and thus the Blitz/wgpu renderer) requires API 26+.
minSdk = 26
targetSdk = 34
// Overridable from CI so each Play upload gets a fresh, increasing code.
versionCode = (System.getenv("LOKI_VERSION_CODE") ?: "1").toInt()
versionName = System.getenv("LOKI_VERSION_NAME") ?: "0.1.0"
// NOTE: ABIs are NOT filtered here — the set of architectures shipped is
// whatever scripts/build-aab.sh stages into src/main/jniLibs/<abi>/.
// That keeps "build for one ABI" vs "build universal" a script concern.
}

signingConfigs {
// Defaults to the Android debug keystore so a plain build yields an
// installable (bundletool-testable) AAB out of the box. For a real Play
// upload, point these env vars at your upload keystore.
create("upload") {
val home = System.getProperty("user.home")
storeFile = file(System.getenv("LOKI_KEYSTORE") ?: "$home/.android/debug.keystore")
storePassword = System.getenv("LOKI_KEYSTORE_PASS") ?: "android"
keyAlias = System.getenv("LOKI_KEY_ALIAS") ?: "androiddebugkey"
keyPassword = System.getenv("LOKI_KEY_PASS") ?: "android"
}
}

buildTypes {
getByName("release") {
// FilePickerActivity is resolved by fully-qualified name over JNI from
// Rust (loki-file-access). R8/ProGuard would rename or strip it and
// break file open/save, so minification stays OFF.
isMinifyEnabled = false
signingConfig = signingConfigs.getByName("upload")
}
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

// targetSdk 34 can trip the ExpiredTargetSdkVersion lint to an error in
// later years; do not let lint fail the bundle build.
lint {
abortOnError = false
checkReleaseBuilds = false
}

sourceSets {
getByName("main") {
java.srcDirs("src/main/java")
jniLibs.srcDirs("src/main/jniLibs")
}
}
}
48 changes: 48 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
AndroidManifest.xml — loki-text Gradle AAB wrapper.

Mirrors loki-text/AndroidManifest.xml (the manifest the cargo-apk pipeline
injects). The package id / min / target SDK come from app/build.gradle.kts
(AGP namespace + defaultConfig), so they are intentionally absent here.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<!--
android:hasCode="true" — FilePickerActivity is real Java/DEX code.
Gradle compiles and dexes it automatically (no manual javac/d8 step).
-->
<application
android:label="Loki"
android:hasCode="true"
android:allowBackup="true">

<!-- Main app entry point: the Rust cdylib libloki_text.so. -->
<activity
android:name="android.app.NativeActivity"
android:label="Loki"
android:exported="true"
android:configChanges="orientation|keyboardHidden|screenSize">
<meta-data
android:name="android.app.lib_name"
android:value="loki_text" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<!--
FilePickerActivity — transparent Java trampoline for SAF file picking.
NativeActivity has no onActivityResult; this Activity receives the SAF
result and delivers it to Rust via JNI. Started only from within the
app, so exported="false".
-->
<activity
android:name="io.github.appthere.lokifileaccess.FilePickerActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:exported="false" />

</application>

</manifest>
23 changes: 23 additions & 0 deletions android/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: Apache-2.0
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
// Versions match the toolchain dx 0.7.9 generates and that is known to
// build successfully in this environment (Gradle 9.1.0 wrapper).
classpath("com.android.tools.build:gradle:8.7.0")
}
}

allprojects {
repositories {
google()
mavenCentral()
}
}

tasks.register("clean") {
delete(rootProject.layout.buildDirectory)
}
5 changes: 5 additions & 0 deletions android/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# SPDX-License-Identifier: Apache-2.0
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
android.useAndroidX=true
android.nonTransitiveRClass=true
android.nonFinalResIds=false
Binary file added android/gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
7 changes: 7 additions & 0 deletions android/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Loading
Loading