Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
101 commits
Select commit Hold shift + click to select a range
0361792
chore: update explore agent reasoning depth
CDGuth Jul 31, 2026
2a6b931
feat(mobile): scaffold MVP navigation and routes
CDGuth Jul 31, 2026
65a75ac
feat(mobile): add marching field and drill domain
CDGuth Aug 1, 2026
12d72fd
feat(mobile): add drill and settings persistence
CDGuth Aug 1, 2026
4668398
feat(mobile): implement core application settings
CDGuth Aug 1, 2026
76b0035
chore(mobile): stabilize MVP foundation
CDGuth Aug 1, 2026
12808a4
feat(drill): scaffold manual drill workflows
CDGuth Aug 1, 2026
e6264e6
feat(drill): implement drill management
CDGuth Aug 1, 2026
6ea225c
feat(drill): add manual page coordinate entry
CDGuth Aug 1, 2026
9ec44a7
feat(drill): add page ordering and transition analysis
CDGuth Aug 1, 2026
d93b0e0
chore(drill): stabilize manual drill MVP
CDGuth Aug 1, 2026
6c5c145
feat(field): add Skia field and camera
CDGuth Aug 1, 2026
4efb251
feat(field): implement responsive field screen
CDGuth Aug 1, 2026
ec31273
feat(field): add live and drill coordinate HUD
CDGuth Aug 1, 2026
59a263c
feat(field): add circular page selector
CDGuth Aug 1, 2026
57ff9d8
feat(field): add drill target and guidance
CDGuth Aug 1, 2026
6cd319b
feat(pans): connect live tag position to field
CDGuth Aug 1, 2026
e1d2adc
feat(settings): add developer mode and diagnostics
CDGuth Aug 1, 2026
06d096f
feat(pans): add anchor position editing
CDGuth Aug 1, 2026
2a4b95a
feat(field): add developer anchor overlays
CDGuth Aug 1, 2026
3aa6e35
fix(mobile): polish MVP integration
CDGuth Aug 1, 2026
b335e62
perf(ci): Enable Gradle caching and parallelism
CDGuth Aug 1, 2026
a2ba50e
perf(ci): Cache iOS native compilation
CDGuth Aug 1, 2026
d735acf
build(mobile): Add EAS app variants
CDGuth Aug 1, 2026
2173a57
feat(drill-schema): Add portable drill document v1
CDGuth Aug 2, 2026
492f6ba
refactor(field): Separate drill grid from physical coordinates
CDGuth Aug 2, 2026
4d23e45
refactor(drill): Migrate mobile drill data to sets
CDGuth Aug 2, 2026
cd50b65
fix(drill-schema): Accept Pyware performer symbols
CDGuth Aug 2, 2026
685e5b0
feat(drill-importers): Parse coordinate sheet PDFs
CDGuth Aug 2, 2026
4a8e737
fix(drill-importers): Honor field conventions
CDGuth Aug 2, 2026
84e215a
chore(workspaces): Pin internal drill dependencies
CDGuth Aug 2, 2026
90f7c9d
feat(drill-converter): Add browser PDF converter
CDGuth Aug 2, 2026
eccd0de
fix(drill-schema): Use canonical blue preset
CDGuth Aug 2, 2026
14fe414
fix(ui): Use Eight2Five fonts consistently
CDGuth Aug 2, 2026
103a4c3
chore(deps): update package-lock.json
CDGuth Aug 2, 2026
1f0f9c9
fix(drill-converter): Destroy PDF loading task
CDGuth Aug 2, 2026
ee1e7c1
fix(drill-importers): Split four-up coordinate sheets
CDGuth Aug 2, 2026
81f81e3
style: Apply lint fixes
CDGuth Aug 2, 2026
cb6e558
feat(drill-converter): Expand preview lists
CDGuth Aug 2, 2026
b22964e
build(testbed): Remove production EAS profile
CDGuth Aug 3, 2026
9069661
chore(schema): Move drill schema host to eight2five.com
CDGuth Aug 3, 2026
9b1293a
ci(ios): Provide Apple team for EAS builds
CDGuth Aug 3, 2026
ce4c963
ci(ios): Load Apple team ID from EAS
CDGuth Aug 3, 2026
63ac596
fix(navigation): Prevent dark-mode back button flash
CDGuth Aug 3, 2026
39e29bd
refactor(settings): Consolidate advanced options
CDGuth Aug 3, 2026
3e6d2d4
fix(runtime): Remove Expo and Skia deprecation warnings
CDGuth Aug 3, 2026
c6e4c8f
feat(field): support schema-driven marching grids
CDGuth Aug 4, 2026
0203f42
feat(mobile): Add drill file upload flow
CDGuth Aug 4, 2026
fa0b15d
feat(mobile): Support multi-performer drill imports
CDGuth Aug 4, 2026
276611f
chore: update android foreground images to be visually consistent wit…
CDGuth Aug 4, 2026
6dfcbcf
fix(mobile): Enforce portrait orientation outside Field
CDGuth Aug 4, 2026
e3418f5
fix(mobile): keep settings tab after drill toggle
CDGuth Aug 4, 2026
16152cb
feat(drill-converter): Expand drill editing controls
CDGuth Aug 5, 2026
8d76498
feat(drill-converter): Edit entity identities
CDGuth Aug 5, 2026
d34aec9
chore(expo): Align SDK dependencies
CDGuth Aug 5, 2026
8c78165
chore(expo): Align linking peer version
CDGuth Aug 5, 2026
c1db4fb
feat(settings): Add theme and field display preferences
CDGuth Aug 5, 2026
08646fb
fix(settings): Apply terminology preference
CDGuth Aug 5, 2026
a81193c
fix(settings): Remove fixed set terminology
CDGuth Aug 5, 2026
1e5d6d9
fix(settings): Label native picker targets
CDGuth Aug 5, 2026
fd11326
feat(drill-schema): Model preset field markings
CDGuth Aug 5, 2026
d954a06
fix(drill-schema): Version custom marking contract
CDGuth Aug 5, 2026
2263700
fix(ui): Guard drill color synchronization
CDGuth Aug 5, 2026
bb02b6f
fix(drill-converter): Share canonical accent color
CDGuth Aug 5, 2026
5a89e1a
fix(field): Correct football markings and camera bounds
CDGuth Aug 5, 2026
33960f0
feat(drill): preserve imported drill documents
CDGuth Aug 5, 2026
842b640
refactor(drill): make drill tab upload and selection only
CDGuth Aug 5, 2026
3cde75d
feat(drill): derive transition marker paths
CDGuth Aug 5, 2026
c4855f7
feat(field): render drill entities and transition markers
CDGuth Aug 5, 2026
c5ff191
feat(mobile): add PANS commissioning and IMU fusion
CDGuth Aug 5, 2026
5dc89cd
feat(mobile): add app info tab
CDGuth Aug 5, 2026
ca053e4
refactor(field): unify drill hud metric modes
CDGuth Aug 5, 2026
2e115d7
feat(field): add expandable drill pill
CDGuth Aug 5, 2026
69b2f48
feat(field): redesign set control dial
CDGuth Aug 5, 2026
421e381
feat(field): separate live and drill hud
CDGuth Aug 5, 2026
5f39579
fix(mobile): polish field mvp interactions
CDGuth Aug 5, 2026
27edf8b
fix(drill-converter): include UniWind type declarations
CDGuth Aug 6, 2026
aa46969
refactor(mobile): remove Info tab
CDGuth Aug 6, 2026
b3caea3
fix(ui): add alt text to message images
CDGuth Aug 6, 2026
fe7974a
fix(storage): Repair mobile database recovery
CDGuth Aug 6, 2026
90e76f6
fix(field): Correct yard numbers and dial regressions
CDGuth Aug 6, 2026
571a88f
fix(drill): Polish upload controls
CDGuth Aug 6, 2026
fab7c08
fix(field): Restore drill metric switching
CDGuth Aug 6, 2026
9bd4db0
fix(field): Repair tag connection modal lifecycle
CDGuth Aug 6, 2026
42eb5a5
fix(mobile): Refine field and developer controls
CDGuth Aug 6, 2026
32e94bf
fix(mobile): Repair field controls and transition rendering
CDGuth Aug 6, 2026
2e4c469
fix(field): repair page dial seam and startup crash
CDGuth Aug 6, 2026
341338f
feat(mobile): refine field HUD and drill controls
CDGuth Aug 6, 2026
39911cd
fix(field): Refine drill HUD spacing
CDGuth Aug 6, 2026
c172383
fix(mobile): Refine field HUD and drill actions
CDGuth Aug 6, 2026
c99ff81
fix(field): Correct sideline labels and hash cadence
CDGuth Aug 6, 2026
0ac83cc
fix(field): Scale position markers with the grid
CDGuth Aug 6, 2026
315b712
fix(mobile): Refine drill card and HUD sizing
CDGuth Aug 6, 2026
8986e08
feat(pans): Add local anchor display names
CDGuth Aug 6, 2026
a6a8f43
fix(drill): Respect imported entity visibility
CDGuth Aug 6, 2026
c88a4c1
fix(mobile): Tighten landscape controls and add sparkle icon
CDGuth Aug 6, 2026
6d85313
fix(mobile): Refine drill controls and dial wrapping
CDGuth Aug 6, 2026
4ed5c97
fix(field): Balance landscape control padding
CDGuth Aug 6, 2026
2775f13
fix(settings): Simplify tag page title
CDGuth Aug 6, 2026
225a04d
fix(pans): Broadcast anchor names from hardware
CDGuth Aug 6, 2026
aad3006
chore: Bump workspace versions to 0.1.0
CDGuth Aug 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
22 changes: 22 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,34 @@ jobs:
node-version: "20"
cache: npm

- name: Restore iOS compiler cache
if: ${{ inputs.platform == 'ios' }}
uses: actions/cache@v4
with:
path: ~/Library/Caches/ccache
key: ios-ccache-${{ inputs.app }}-${{ github.sha }}
restore-keys: |
ios-ccache-${{ inputs.app }}-

- name: Install ccache
if: ${{ inputs.platform == 'ios' }}
shell: bash
run: |
set -euo pipefail
command -v ccache >/dev/null 2>&1 || brew install ccache
ccache --version

- name: Setup Java
if: ${{ inputs.platform == 'android' }}
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: "17"

- name: Setup Gradle
if: ${{ inputs.platform == 'android' }}
uses: gradle/actions/setup-gradle@v6

- name: Setup Android SDK
if: ${{ inputs.platform == 'android' }}
uses: android-actions/setup-android@v4
Expand Down Expand Up @@ -218,6 +239,7 @@ jobs:
working-directory: ${{ steps.resolve.outputs.app_dir }}
env:
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
EXPO_APPLE_TEAM_TYPE: INDIVIDUAL
EAS_LOCAL_BUILD_ARTIFACTS_DIR: ${{ steps.resolve.outputs.artifact_dir }}
shell: bash
run: |
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ coverage/clover.xml

# other
.com.facebook.react.devsupport.BundleDownloader.swp
/app.json

# local vendor reference docs (do not commit)
docs/local-only
Expand Down
2 changes: 1 addition & 1 deletion .opencode/agents/explore.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ permission:
context7_query-docs: allow
markitdown_convert_to_markdown: allow
model: openai/gpt-5.6-luna
variant: max
variant: high
---

You are a read-only exploration and research specialist supporting the primary engineer agent.
Expand Down
6 changes: 6 additions & 0 deletions apps/drill-converter/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

# @generated expo-cli sync-2b81b286409207a5da26e14c78851eb30d8ccbdb
# The following patterns were generated by expo-cli

expo-env.d.ts
# @end expo-cli
19 changes: 19 additions & 0 deletions apps/drill-converter/app.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { ExpoConfig } from "expo/config";

const config: ExpoConfig = {
name: "Eight2Five Drill Converter",
slug: "eight2five-drill-converter",
version: "0.1.0",
platforms: ["web"],
userInterfaceStyle: "automatic",
web: {
bundler: "metro",
output: "static",
},
plugins: ["expo-router"],
experiments: {
typedRoutes: true,
},
};

export default config;
15 changes: 15 additions & 0 deletions apps/drill-converter/app/_layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from "react";
import { Stack } from "expo-router";

export default function RootLayout() {
return (
<Stack
screenOptions={{
headerTitle: "Eight2Five Drill Converter",
headerShadowVisible: false,
headerStyle: { backgroundColor: "#ffffff" },
headerTintColor: "#172033",
}}
/>
);
}
13 changes: 13 additions & 0 deletions apps/drill-converter/app/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from "react";
import { Stack } from "expo-router";

import { ConverterScreen } from "../src/converter-screen";

export default function IndexRoute() {
return (
<>
<Stack.Screen options={{ title: "Drill Converter" }} />
<ConverterScreen />
</>
);
}
19 changes: 19 additions & 0 deletions apps/drill-converter/metro.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const { getDefaultConfig } = require("expo/metro-config");
const path = require("path");

const projectRoot = __dirname;
const workspaceRoot = path.resolve(__dirname, "../..");
const config = getDefaultConfig(projectRoot);

config.watchFolders = Array.from(
new Set([...(config.watchFolders ?? []), workspaceRoot])
);
config.resolver.nodeModulesPaths = Array.from(
new Set([
...(config.resolver?.nodeModulesPaths ?? []),
path.resolve(projectRoot, "node_modules"),
path.resolve(workspaceRoot, "node_modules"),
])
);

module.exports = config;
52 changes: 52 additions & 0 deletions apps/drill-converter/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"name": "eight2five-drill-converter",
"version": "0.1.0",
"private": true,
"main": "expo-router/entry",
"scripts": {
"start": "expo start --web",
"web": "expo start --web",
"build:web": "expo export --platform web",
"type-check": "tsc --noEmit",
"lint": "expo lint",
"lint:fix": "expo lint --fix",
"test": "jest --watchAll=false --passWithNoTests --runInBand"
},
"dependencies": {
"@eight2five/drill-importers": "0.1.0",
"@eight2five/drill-schema": "0.1.0",
"@eight2five/ui": "0.1.0",
"@expo/metro-runtime": "~57.0.8",
"expo": "~57.0.10",
"expo-document-picker": "~57.0.1",
"expo-router": "~57.0.10",
"lucide-react-native": "^1.22.0",
"react": "19.2.3",
"react-dom": "^19.2.3",
"react-native": "0.86.2",
"react-native-safe-area-context": "~5.7.0",
"react-native-web": "^0.21.2"
},
"devDependencies": {
"@types/jest": "^29.5.14",
"@types/react": "~19.2.10",
"eslint": "^9.0.0",
"eslint-config-expo": "~57.0.1",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.4",
"jest": "^29.7.0",
"jest-expo": "~57.0.3",
"prettier": "^3.6.2",
"typescript": "~6.0.3"
},
"jest": {
"preset": "jest-expo",
"testEnvironment": "node",
"moduleNameMapper": {
"^@eight2five/drill-schema$": "<rootDir>/../../packages/drill-schema/src/index.ts",
"^@eight2five/drill-schema/(.*)$": "<rootDir>/../../packages/drill-schema/src/$1",
"^@eight2five/drill-importers$": "<rootDir>/../../packages/drill-importers/src/index.ts",
"^@eight2five/drill-importers/(.*)$": "<rootDir>/../../packages/drill-importers/src/$1"
}
}
}
162 changes: 162 additions & 0 deletions apps/drill-converter/src/components/details-section.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import React from "react";
import { Text, View } from "react-native";
import {
getFieldPreset,
getGridReference,
type FieldPresetId,
} from "@eight2five/drill-schema";

import {
ChoiceChips,
Disclosure,
FormField,
SectionCard,
} from "../ui/form-controls";
import { colors, radius, spacing } from "../ui/theme";
import {
FIELD_PRESET_OPTIONS,
type ConverterSettings,
} from "../converter/settings";

const FIELD_OPTIONS = Object.freeze([
...FIELD_PRESET_OPTIONS,
{ value: "custom", label: "Custom" },
] as const);

export function DetailsSection({
settings,
errors,
onUpdate,
}: {
readonly settings: ConverterSettings;
readonly errors: readonly string[];
readonly onUpdate: (patch: Partial<ConverterSettings>) => void;
}) {
const titleError = errors.find((error) => error.includes("title"));
const customFieldError = errors.find((error) =>
error.startsWith("Custom field"),
);
return (
<SectionCard
title="2. Drill details"
description="The converter only asks for the information needed to produce a valid, useful drill file. Less common schema fields stay under optional sections."
>
<FormField
label="Title"
value={settings.title}
onChangeText={(title) => onUpdate({ title })}
placeholder="Part 4"
error={titleError}
/>

<ChoiceChips
label="Field convention"
value={settings.fieldMode}
options={FIELD_OPTIONS}
onChange={(fieldMode) => onUpdate({ fieldMode })}
/>

{settings.fieldMode === "custom" ? (
<FormField
label="Custom field definition"
value={settings.customFieldJson}
onChangeText={(customFieldJson) => onUpdate({ customFieldJson })}
multiline
autoCapitalize="none"
autoCorrect={false}
spellCheck={false}
error={customFieldError}
helper="Advanced: paste a custom field object with matched physical and marching reference lines. The editor starts from NFHS geometry so you can change only what differs."
/>
) : (
<FieldPresetSummary preset={settings.fieldMode} />
)}

<Disclosure
title="Optional metadata"
description="Writer, ensemble, description, and an optional Lucide icon name."
>
<FormField
label="Drill writer"
value={settings.drillWriter}
onChangeText={(drillWriter) => onUpdate({ drillWriter })}
placeholder="Optional"
/>
<FormField
label="Ensemble"
value={settings.ensemble}
onChangeText={(ensemble) => onUpdate({ ensemble })}
placeholder="Optional"
/>
<FormField
label="Description"
value={settings.description}
onChangeText={(description) => onUpdate({ description })}
multiline
placeholder="Optional notes about this movement"
/>
<FormField
label="Lucide icon"
value={settings.lucideIcon}
onChangeText={(lucideIcon) => onUpdate({ lucideIcon })}
autoCapitalize="none"
autoCorrect={false}
placeholder="music-2"
helper="Optional kebab-case Lucide icon name stored as metadata. The converter does not download icon assets."
/>
</Disclosure>
</SectionCard>
);
}

function FieldPresetSummary({ preset }: { readonly preset: FieldPresetId }) {
const field = getFieldPreset(preset);
const frontHash = getGridReference({ type: "preset", preset }, "front-hash");
const backHash = getGridReference({ type: "preset", preset }, "back-hash");
const backSideline = getGridReference(
{ type: "preset", preset },
"back-sideline",
);
return (
<View
style={{
gap: spacing.xs,
borderWidth: 1,
borderColor: colors.border,
borderRadius: radius.sm,
padding: spacing.md,
backgroundColor: colors.surfaceMuted,
}}
>
<Text
selectable
style={{ color: colors.text, fontSize: 13, fontWeight: "700" }}
>
{field.name}
</Text>
<Text
selectable
style={{ color: colors.textMuted, fontSize: 12, lineHeight: 17 }}
>
Origin: center of the 50 on the front sideline · Side 1 is negative X ·
Side 2 is positive X · backfield is positive Y.
</Text>
<Text
selectable
style={{ color: colors.textMuted, fontSize: 12, lineHeight: 17 }}
>
Marching grid: Front sideline 0 · Front hash{" "}
{formatNumber(frontHash?.coordinateSteps)} · Back hash{" "}
{formatNumber(backHash?.coordinateSteps)} · Back sideline{" "}
{formatNumber(backSideline?.coordinateSteps)}.
</Text>
</View>
);
}

function formatNumber(value: number | undefined): string {
if (value === undefined) return "—";
return Number.isInteger(value)
? String(value)
: value.toFixed(3).replace(/0+$/, "").replace(/\.$/, "");
}
Loading
Loading