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
82 changes: 51 additions & 31 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ concurrency:
cancel-in-progress: true

jobs:
lint:
name: Lint
js-lint:
name: '[JS] Format & Lint'
runs-on: ubuntu-latest
steps:
- name: Checkout code
Expand All @@ -29,14 +29,14 @@ jobs:
- name: Install dependencies
run: npm ci

- name: Check formatting
run: npm run format:check
- name: Check JS formatting
run: npm run format:js:check

- name: Run linter
- name: Run JS linter
run: npm run lint:libOnly

typecheck:
name: Type Check
name: '[JS] Types validation'
runs-on: ubuntu-latest
steps:
- name: Checkout code
Expand All @@ -55,7 +55,7 @@ jobs:
run: npx tsc --noEmit

test:
name: Test JavaScript
name: '[JS] Test'
runs-on: ubuntu-latest
steps:
- name: Checkout code
Expand All @@ -74,12 +74,21 @@ jobs:
run: npm run test:js

native-kotlin-test:
name: Test Kotlin
name: '[Kotlin] Test'
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Java
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '17'

- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
Expand All @@ -99,7 +108,7 @@ jobs:
run: npm run test:kotlin

native-swift-test:
name: Test Swift
name: '[Swift] Test'
runs-on: macos-latest
steps:
- name: Checkout code
Expand All @@ -118,7 +127,7 @@ jobs:
run: npm run test:swift

swiftformat:
name: SwiftFormat Check
name: '[Swift] Format & Lint'
runs-on: macos-latest
steps:
- name: Checkout code
Expand All @@ -133,24 +142,35 @@ jobs:
- name: Check Swift formatting
run: npm run format:swift:check

# TODO: Add ktlint back at some point (it takes over 2 minutes to install)
# ktlint:
# name: ktlint Check
# runs-on: ubuntu-latest
# steps:
# - name: Checkout code
# uses: actions/checkout@v4

# - name: Set up Node.js
# uses: actions/setup-node@v4
# with:
# node-version: '22'
# cache: 'npm'

# - name: Install ktlint
# run: |
# eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
# brew install ktlint

# - name: Check Kotlin formatting
# run: npm run format:kotlin:check
ktlint:
name: '[Kotlin] Format & Lint'
runs-on: ubuntu-latest
env:
KTLINT_VERSION: 1.8.0
KTLINT_SHA256: a3fd620207d5c40da6ca789b95e7f823c54e854b7fade7f613e91096a3706d75
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'

# Downloading the pinned release binary is much faster in CI than installing ktlint via Homebrew.
- name: Install ktlint
run: |
mkdir -p "$RUNNER_TEMP/bin"
curl -fsSL \
"https://github.com/pinterest/ktlint/releases/download/${KTLINT_VERSION}/ktlint" \
-o "$RUNNER_TEMP/bin/ktlint"
echo "${KTLINT_SHA256} $RUNNER_TEMP/bin/ktlint" | sha256sum --check --
chmod +x "$RUNNER_TEMP/bin/ktlint"
echo "$RUNNER_TEMP/bin" >> "$GITHUB_PATH"

- name: Check ktlint version
run: ktlint --version

- name: Check Kotlin formatting
run: npm run format:kotlin:check
4 changes: 3 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
/build
/android/build
/example/.expo
/example/ios
/ios/.build
/plugin/build
/.eslintrc.js
/.expo
/website/doc_build
/website/docs
/website/docs
8 changes: 4 additions & 4 deletions android/src/test/java/voltra/SmokeTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import org.junit.Assert.assertEquals
import org.junit.Test

class SmokeTest {
@Test
fun oneEqualsOne() {
assertEquals(1, 1)
}
@Test
fun oneEqualsOne() {
assertEquals(1, 1)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,7 @@ export default function ChannelUpdatesTestingScreen() {
<View style={styles.container}>
<ScrollView style={styles.scrollView} contentContainerStyle={styles.content}>
<Text style={styles.heading}>Channel-Based Updates</Text>
<Text style={styles.subheading}>
Start a minimal Live Activity subscribed to a specific broadcast channel.
</Text>
<Text style={styles.subheading}>Start a minimal Live Activity subscribed to a specific broadcast channel.</Text>

<Card>
<Card.Title>Live Activity Channel</Card.Title>
Expand Down
3 changes: 1 addition & 2 deletions example/screens/testing-grounds/TestingGroundsScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,7 @@ const TESTING_GROUNDS_SECTIONS = [
{
id: 'channel-updates',
title: 'Channel-Based Updates',
description:
'Start a minimal Live Activity bound to a broadcast channel ID and test server-driven updates.',
description: 'Start a minimal Live Activity bound to a broadcast channel ID and test server-driven updates.',
route: '/testing-grounds/channel-updates',
},
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,9 @@ export default function GradientPlaygroundScreen() {
{/* rgba inside gradient */}
<Card>
<Card.Title>RGBA Inside Gradient</Card.Title>
<Text style={styles.previewSubtext}>linear-gradient(to right, rgba(255,0,0,0.8) 0%, rgba(0,0,255,0.3) 100%)</Text>
<Text style={styles.previewSubtext}>
linear-gradient(to right, rgba(255,0,0,0.8) 0%, rgba(0,0,255,0.3) 100%)
</Text>

<VoltraView style={{ width: '100%', height: 80, backgroundColor: '#0F172A', padding: 16, marginTop: 12 }}>
<Voltra.View
Expand Down
44 changes: 21 additions & 23 deletions generator/generate-types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/bin/env node
import { execSync } from 'node:child_process'
import * as fs from 'node:fs'
import * as path from 'node:path'

Expand Down Expand Up @@ -40,6 +41,21 @@ const writeFiles = (outputDir: string, files: Record<string, string>) => {
}
}

const runFormatScript = (scriptName: string, stepLabel: string) => {
console.log(`Step ${stepLabel}: Running npm run ${scriptName}...`)
try {
const result = execSync(`npm run ${scriptName}`, { encoding: 'utf-8', cwd: ROOT_DIR })
if (result.trim()) {
console.log(result.trim())
}
} catch (error: any) {
console.warn(` Warning: npm run ${scriptName} exited with code ${error.status ?? 'unknown'}`)
if (error.stdout) console.log(' stdout:', error.stdout.trim())
if (error.stderr) console.log(' stderr:', error.stderr.trim())
}
console.log()
}

const main = () => {
console.log('🚀 Generating types from schemas...\n')

Expand Down Expand Up @@ -67,29 +83,6 @@ const main = () => {
writeFiles(TS_PROPS_OUTPUT_DIR, tsJsxResult.props)
console.log()

// Step 4: Lint and fix generated TypeScript files
console.log('Step 4: Running eslint --fix on generated TypeScript files...')
const generatedTsFiles = Object.keys(tsJsxResult.props).map((filename) => path.join(TS_PROPS_OUTPUT_DIR, filename))
if (generatedTsFiles.length > 0) {
const eslintCommand = `npx eslint --fix ${generatedTsFiles.join(' ')}`
try {
const result = require('child_process').execSync(eslintCommand, { encoding: 'utf-8', cwd: ROOT_DIR })
if (result) {
console.log(' ESLint output:', result.trim())
}
} catch (error: any) {
// ESLint might exit with code 1 if it fixed issues, which is normal
if (error.status !== 0 && error.status !== 1) {
console.warn(' Warning: ESLint exited with code', error.status)
if (error.stdout) console.log(' stdout:', error.stdout.trim())
if (error.stderr) console.log(' stderr:', error.stderr.trim())
} else if (error.stdout) {
console.log(' ESLint output:', error.stdout.trim())
}
}
}
console.log()

// Step 5: Generate Swift parameter types
console.log('Step 5: Generating Swift parameter types...')
const swiftParameterFiles = generateSwiftParameters(componentsData)
Expand Down Expand Up @@ -149,6 +142,11 @@ const main = () => {
writeFiles(KOTLIN_GENERATED_DIR, kotlinShortNameFiles)
console.log()

// Step 8: Format generated output
runFormatScript('format:js:fix', '8')
runFormatScript('format:kotlin:fix', '9')
runFormatScript('format:swift:fix', '10')

console.log('✅ Generation complete!\n')
console.log('Generated files:')
console.log(
Expand Down
2 changes: 1 addition & 1 deletion ios/Tests/VoltraSharedTests/SmokeTests.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import XCTest
@testable import VoltraSharedCore
import XCTest

final class SmokeTests: XCTestCase {
func testOneEqualsOne() {
Expand Down
2 changes: 1 addition & 1 deletion ios/shared/VoltraEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Foundation

/// All Voltra event types with their associated data
public enum VoltraEventType {
// Persistent events (widget → app, survives app death)
/// Persistent events (widget → app, survives app death)
case interaction(source: String, identifier: String, payload: String?)

// Transient events (main app only, in-memory)
Expand Down
4 changes: 2 additions & 2 deletions ios/shared/VoltraInteractionIntent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ public struct VoltraInteractionIntent: LiveActivityIntent {
public static var title: LocalizedStringResource = "Interact"
public static var isDiscoverable: Bool = false

// The ID of the activity
/// The ID of the activity
@Parameter(title: "Activity ID")
var activityId: String

// The ID of the component in your JSON (e.g., "button_1", "pause_btn")
/// The ID of the component in your JSON (e.g., "button_1", "pause_btn")
@Parameter(title: "Component ID")
var componentId: String

Expand Down
12 changes: 1 addition & 11 deletions ios/target/VoltraHomeWidget.swift
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public enum VoltraHomeWidgetStore {
}
}

// Timeline data structures (intermediate storage - still uses Data for flexibility)
/// Timeline data structures (intermediate storage - still uses Data for flexibility)
public struct WidgetTimelineEntry {
let date: Date
let json: Data
Expand All @@ -130,15 +130,6 @@ public struct VoltraHomeWidgetEntry: TimelineEntry, Equatable {
self.widgetId = widgetId
self.deepLinkUrl = deepLinkUrl
}

public static func == (lhs: VoltraHomeWidgetEntry, rhs: VoltraHomeWidgetEntry) -> Bool {
// VoltraNode is Hashable, so this comparison works correctly.
// If the parsed AST is different, entries are not equal → WidgetKit re-renders.
lhs.date == rhs.date &&
lhs.rootNode == rhs.rootNode &&
lhs.widgetId == rhs.widgetId &&
lhs.deepLinkUrl == rhs.deepLinkUrl
}
}

public struct VoltraHomeWidgetProvider: TimelineProvider {
Expand Down Expand Up @@ -240,7 +231,6 @@ public struct VoltraHomeWidgetView: View {
.disableWidgetMarginsIfAvailable()
}

@ViewBuilder
private func placeholderView(widgetId _: String) -> some View {
VStack(alignment: .leading, spacing: 8) {
Text("Almost ready")
Expand Down
2 changes: 1 addition & 1 deletion ios/tests/JSGradientParserTests.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import XCTest
import SwiftUI
@testable import VoltraStyleCore
import XCTest

final class JSGradientParserTests: XCTestCase {
private func assertLinearGradient(
Expand Down
29 changes: 23 additions & 6 deletions ios/ui/Layout/VoltraFlexStackLayout.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,14 @@ struct VoltraFlexStackLayout: Layout {
let mainMarginTrailing: CGFloat
let crossMarginLeading: CGFloat
let crossMarginTrailing: CGFloat
var mainMargin: CGFloat { mainMarginLeading + mainMarginTrailing }
var crossMargin: CGFloat { crossMarginLeading + crossMarginTrailing }
var mainMargin: CGFloat {
mainMarginLeading + mainMarginTrailing
}

var crossMargin: CGFloat {
crossMarginLeading + crossMarginTrailing
}

let alignSelf: FlexAlign?
}

Expand Down Expand Up @@ -325,10 +331,21 @@ struct VoltraFlexStackLayout: Layout {
: containerPadding.leading + containerPadding.trailing
}

private var leadingPad: CGFloat { containerPadding.leading }
private var trailingPad: CGFloat { containerPadding.trailing }
private var topPad: CGFloat { containerPadding.top }
private var bottomPad: CGFloat { containerPadding.bottom }
private var leadingPad: CGFloat {
containerPadding.leading
}

private var trailingPad: CGFloat {
containerPadding.trailing
}

private var topPad: CGFloat {
containerPadding.top
}

private var bottomPad: CGFloat {
containerPadding.bottom
}

private var crossPaddingLeading: CGFloat {
axis == .horizontal ? containerPadding.top : containerPadding.leading
Expand Down
4 changes: 3 additions & 1 deletion ios/ui/Protocols/VoltraView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,7 @@ public struct EmptyParameters: ComponentParameters {}

/// Convenience extension for views with no parameters
public extension VoltraView where Parameters == EmptyParameters {
var params: EmptyParameters { EmptyParameters() }
var params: EmptyParameters {
EmptyParameters()
}
}
1 change: 0 additions & 1 deletion ios/ui/Style/DecorationStyle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ struct DecorationModifier: ViewModifier {
}
}

@ViewBuilder
private func radialGradientBackground(_ spec: RadialGradientSpec) -> some View {
GeometryReader { proxy in
let size = proxy.size
Expand Down
Loading
Loading