Open Chrome Browser -> Enter chrome://flags

Enable Vertical Tabs in Experiments

View -> Click Show Tabs Vertically

That’s it. You can see Vertical Tabs in Chrome Browser.
Open Chrome Browser -> Enter chrome://flags

Enable Vertical Tabs in Experiments

View -> Click Show Tabs Vertically

That’s it. You can see Vertical Tabs in Chrome Browser.

I’ve used fork app for more than 5 years now.
This app is light, built by native. It’s fast and easy to manage git branches and commits.

It also supports the command line tools. I only use fork in terminal to open the current folder in fork app. It’s quite useful when you are working on terminal with claude code.
CoreData supports 3 type of migration

Case 1. Manual Migration
When you faced this issue, You need to check next version of the xcdatamodel.
Case 2. LightWeight Migration
Most common crash issues are caused by mismatching options in a properties. For example you have a Binary Data type field with external storage. And when your next xcdatamodel doesn’t matching with external storage options, it causes crash issues
Lastly I recommend adding Arguments to investigate issues

Follow the official guide
brew install redis brew services start redis brew services info redis redis-cli

Add Redis Swift Package
.package(url: "https://github.com/vapor/queues-redis-driver.git", from: "1.0.0")
// swift-tools-version:6.0
import PackageDescription
let package = Package(
name: "TestServer",
platforms: [
.macOS(.v13)
],
dependencies: [
// 💧 A server-side Swift web framework.
.package(url: "https://github.com/vapor/vapor.git", from: "4.111.0"),
.package(url: "https://github.com/vapor/fluent", from: "4.12.0"),
.package(url: "https://github.com/vapor/fluent-sqlite-driver", from: "4.8.0"),
.package(url: "https://github.com/vapor/sql-kit", from: "3.33.2"),
.package(url: "https://github.com/lukaskubanek/LoremSwiftum", from: "2.2.3"),
.package(url: "https://github.com/vapor/fluent-postgres-driver", from:"2.10.0"),
.package(url: "https://github.com/vapor/jwt", from: "5.1.2"),
.package(url: "https://github.com/vapor/queues-redis-driver.git", from: "1.0.0"),
],
targets: [
.target(
name: "App",
dependencies: [
.product(name: "Vapor", package: "vapor"),
.product(name: "Fluent", package: "fluent"),
.product(name: "FluentSQLiteDriver", package: "fluent-sqlite-driver"),
.product(name: "SQLKit", package: "sql-kit"),
.product(name: "LoremSwiftum", package: "LoremSwiftum"),
.product(name: "JWT", package: "jwt"),
.product(name: "FluentPostgresDriver", package: "fluent-postgres-driver"),
.product(name: "QueuesRedisDriver", package: "queues-redis-driver")
],
swiftSettings: [
// Enable better optimizations when building in Release configuration. Despite the use of
// the `.unsafeFlags` construct required by SwiftPM, this flag is recommended for Release
// builds. See <https://github.com/swift-server/guides#building-for-production> for details.
.unsafeFlags(["-cross-module-optimization"], .when(configuration: .release))
]
),
.executableTarget(name: "Run", dependencies: [
.target(name: "App")
]
),
.testTarget(name: "AppTests", dependencies: [
.target(name: "App"),
.product(name: "XCTVapor", package: "vapor")
])
]
)
import Foundation
import Vapor
import Queues
struct ScheduledJobs: AsyncScheduledJob {
// Add extra services here via dependency injection, if you need them.
func run(context: QueueContext) async throws {
context.logger.info("Starting ScheduledJobs")
print("✅ It is called")
//Call other services using context.application.client
// context.application.client
context.logger.info("ScheduledJobs completed")
}
}
https://docs.vapor.codes/advanced/queues/#available-builder-methods
import Vapor
import QueuesRedisDriver
public func configure(_ app: Application) throws {
let redisConfig = try RedisConfiguration(
hostname: "127.0.0.1",
port: 6379,
pool: .init(
maximumConnectionCount: .maximumActiveConnections(50),
minimumConnectionCount: 10
)
)
app.redis.configuration = redisConfig
//Use Redis
app.queues.use(.redis(redisConfig))
//Register ScheduledJob - It runs every 30 seconds
app.queues.schedule(ScheduledJobs())
.minutely()
.at(30)
try app.queues.startScheduledJobs()
....
}

Hope my articles helps you who want to run scheduled job using Redis. Please like my post or leave a comment it helps me continue share my knowledge for free.

한국 여권은 태국에 90일까지 머물수 있다.
작년에 치앙마이에서 정확하게 97일 연속으로 체류했었다.
참고로 가장 쉬운 방법은 태국을 떠나 다른 나라로 갔다가 다시 태국으로 돌아오는 방법이다. 하지만 나는 그럴 여유가 없었고 영어학원 종료일과 콘도 계약기간이 7일이나 남아있어서 태국에 체류하면서 연장을 해야 했다.
이 포스트는 태국에 머무르면서 1주일 연장 하는 방법에 대한 포스팅이다. (이 포스팅은 2024년 10월 기준 정보이다)
태국은 반드시 90일 이상 체류 시 신고를 해야 된다.

나는 디콘도에 살았었다. 가장 가까운 센트럴 치앙마이 몰에 이미그레이션 센터가 있다.

쇼핑몰 안에 있는 이미그레이션 센터.


먼저 TM30이라는 서류 양식이 있다. 정확하게 어떤 내용을 기입했는지 기억나지 않지만 집 계약문서 혹은 집주인으로부터 발급받아야 되는 걸로 기억한다.

위의 서류는 거주자 신고서 양식이다. 이건 온라인으로 제출했었다.
서류를 미리 작성하고 방문을 하면 이미그레이션 센터 직원에서 서류 검토를 해준다. 검토 후 이상이 없으면 다음과 같은 번호표를 발급해준다.


비자 승인이 완료되면 여권에 위와 같은 도장을 찍어준다.
원래 입국 시 찍어준 도장은 10월 22일까지 체류할 수 있었는데 체류 연장 이후 10월 29일까지 머무를 수 있도록 도장 찍어줬다.


사이트에 가입해서 구매했다. 이전에는 싱가포리언 친구 차를 타고 편하게 갔었는데 이번에 처음으로 기차타고 가보기로 결심.
싱가포르 우드랜드 기차역에서 JB Sentral까지는 금액이 편도로 인당 6.8불이다.

반대로 JB Sentral에서 우드랜드로 돌아오는 표는 오히려 더 저렴하다. 인당 2.14불.
결제는 PayNow로 완료함.

생각보다 어렵지 않은 티케팅. 금액도 아주 저렴하고 만족스럽다. 다음 포스팅에서는 조호바루 여행 후기를 남길 예정.

This post is for whom want to use AWS Lambda with OpenAPI Generator. Official guide is useful but I felt there are some missing information. So I wrote this post. You can successfully run AWS Lambda function on your local machine and debug your code.
Let’s start from very simple example. You only need 4 files. I’ll explain details. (Ignore Tests folder, no need it in this tutorial)

This OpenAPI spec is for tutorial.
Please check folder and file structure. Create an openapi.yaml
openapi: 3.1.0
info:
title: MobileJobService
version: 1.0.0
paths:
/jobs/fetch:
post:
summary: Fetch job data from external source
description: >
This endpoint is called by a scheduled Lambda or backend service.
It fetches job data from the given URL and processes it using the provided prompt.
operationId: fetchJobs
tags:
- jobs
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/FetchJobsRequest'
responses:
'200':
description: Successfully fetched and processed job data
content:
application/json:
schema:
$ref: '#/components/schemas/JobListResponse'
'400':
description: Invalid input parameters
'500':
description: Internal error during job fetch or processing
components:
schemas:
FetchJobsRequest:
type: object
required:
- url
- prompt
properties:
url:
type: string
format: uri
description: Target URL to scrape or fetch job data from
prompt:
type: string
description: Instruction / extraction prompt used to parse the fetched page
JobListResponse:
type: array
items:
$ref: '#/components/schemas/Job'
Job:
type: object
required:
- id
- title
properties:
id:
type: string
description: Unique identifier for the job
title:
type: string
description: Job title
country:
type: string
description: Country code (e.g. SG, US, TW)
city:
type: string
description: City name (e.g. Singapore)
postedAt:
type: string
format: date-time
description: When this job was posted, if known
company:
type: string
description: Company name
team:
type: string
description: Team / department (e.g. Mobile, Backend, Growth)
jobDescriptionLink:
type: string
format: uri
description: Public link to full job description
jobApplyLink:
type: string
format: uri
description: Public link to apply
salary:
$ref: '#/components/schemas/Salary'
description:
type: string
description: Cleaned / extracted full-text description for the role
Salary:
type: object
properties:
min:
type: number
description: Minimum compensation
max:
type: number
description: Maximum compensation
basis:
type: string
enum: [year, month]
description: Salary period basis (yearly or monthly)
And then create openapi-generator-config.yaml
generate: - types - server
Package.swift
// swift-tools-version: 6.2
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "NativeMobileServer",
platforms: [
.macOS(.v15)
],
products: [
.executable(name: "NativeMobileServer", targets: ["NativeMobileServer"])
],
dependencies: [
.package(url: "https://github.com/apple/swift-openapi-generator.git", from: "1.10.3"),
.package(url: "https://github.com/apple/swift-openapi-runtime.git", from: "1.8.2"),
.package(url: "https://github.com/awslabs/swift-aws-lambda-runtime.git", from: "2.0.0"),
.package(url: "https://github.com/awslabs/swift-aws-lambda-events.git", from: "1.2.0"),
.package(url: "https://github.com/awslabs/swift-openapi-lambda.git", from: "2.0.0"),
.package(url: "https://github.com/swift-server/swift-service-lifecycle.git", from: "2.0.0"),
],
targets: [
.executableTarget(
name: "NativeMobileServer",
dependencies: [
.product(name: "OpenAPIRuntime", package: "swift-openapi-runtime"),
.product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime"),
.product(name: "AWSLambdaEvents", package: "swift-aws-lambda-events"),
.product(name: "OpenAPILambda", package: "swift-openapi-lambda"),
.product(name: "ServiceLifecycle", package: "swift-service-lifecycle"),
],
path: "Sources/NativeMobileServer",
resources: [
.copy("openapi.yaml"),
.copy("openapi-generator-config.yaml")
],
plugins: [
.plugin(name: "OpenAPIGenerator", package: "swift-openapi-generator")
]
),
.testTarget(
name: "NativeMobileServerTests",
dependencies: ["NativeMobileServer"]
),
]
)
You can remove .testTarget if you don’t need to write test cases.
//
// NativeMobileServer.swift
// NativeMobileServer
//
// Created by Shawn Sungwook Baek on 10/26/25.
//
import Foundation
import Logging
import OpenAPILambda
import OpenAPIRuntime
@main
struct JobServiceImpl: APIProtocol, OpenAPILambdaHttpApi {
func fetchJobs(_ input: Operations.fetchJobs.Input) async throws -> Operations.fetchJobs.Output
{
logger.info("fetchJobs invoked")
let requestBody = input
if case let .json(request) = requestBody.body {
logger.info("Fetching jobs from \(request.url) with prompt: \(request.prompt)")
}
let mockJobs: [Components.Schemas.Job] = [
.init(
id: "job-001",
title: "iOS Developer",
country: "SG",
postedAt: Date(),
company: "Apple",
team: "Mobile",
jobDescriptionLink: "https://example.com/job/1",
jobApplyLink: "https://example.com/apply/1",
salary: .init(min: 6000, max: 9000, basis: .month),
description: "Develop and maintain the iOS application."
),
.init(
id: "job-002",
title: "Backend Engineer",
country: "TW",
postedAt: Date(),
company: "Uber",
team: "Server",
jobDescriptionLink: "https://example.com/job/2",
jobApplyLink: "https://example.com/apply/2",
salary: .init(min: 7000, max: 10000, basis: .month),
description: "Build API services and integrations."
),
]
return .ok(.init(body: .json(mockJobs)))
}
let logger: Logger
func register(transport: OpenAPILambdaTransport) throws {
try transport.router.get("/health") { _, _ in
"OK"
}
logger.trace("Available Routes\n\(transport.router)")
// to log all requests and their responses, add a logging middleware
let loggingMiddleware = LoggingMiddleware(logger: logger)
// MANDATORY (middlewares are optional)
try self.registerHandlers(on: transport, middlewares: [loggingMiddleware])
}
static func main() async throws {
let openAPIService = JobServiceImpl(loggerLabel: "JobService")
try await openAPIService.run()
}
init(loggerLabel: String) {
var logger = Logger(label: loggerLabel)
logger.logLevel = .trace
self.logger = logger
}
}

Check Executable Target.

Important!
You need to set Environment Variables
Try build and run and check which pid is using 7000 port.
lsof -i :7000
If other program uses that port, you can change default port by setting LOCAL_LAMBDA_PORT

When you run it, you will see the message. (In my case, I changed the default port 8000)
This part you may confusing because you can’t call lambda like
curl -v -X POST http://127.0.0.1:8000/jobs/fetch \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/jobs",
"prompt": "Extract latest mobile job listings"
}'

If you want to see more detail information, check this out
To invoke and debug our code, we should invoke function like this.
If you want to learn more about this payload structure v2.0 in API Gateway check here
curl -X "POST" "http://127.0.0.1:8000/invoke" \
-H 'Content-Type: application/json; charset=utf-8' \
-d $'{
"requestContext": {
"accountId": "",
"time": "",
"http": {
"path": "/jobs/fetch",
"userAgent": "",
"method": "POST",
"protocol": "HTTP/1.1",
"sourceIp": "127.0.0.1"
},
"domainName": "",
"timeEpoch": 0,
"domainPrefix": "",
"apiId": "",
"requestId": "",
"stage": "$default"
},
"rawPath": "/jobs/fetch",
"rawQueryString": "",
"version": "2.0",
"routeKey": "$default",
"isBase64Encoded": true,
"body": "{\\"url\\":\\"https://jobs.apple.com/en-sg/search\\", \\"prompt\\":\\"Extract latest mobile job listings\\"}"
}'
I’ve set breakpoint at here.

Let’s invoke function.

Ok, breakpoint is working.

And I can see the results from AWS Lambda function
Swift is very powerful for developing server-side applications. There a lot of great open source projects. In Part 2, I’ll explain how to deploy AWS Lambda Swift function to the AWS using SAM CLI.

Swift Format is made by Apple. If your Xcode Version is latest version (after Xcode 16), you don’t need to install it. Toolchain contains swift format.
xcrun --find swift-format ///Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift-format
https://github.com/swiftlang/swift-format?tab=readme-ov-file#configuring-the-command-line-tool
swift-format dump-configuration > swift-format.json //It will show default settings for formatting and listing
Copied default settings and create a file like swift-format.json in your project root directory.
SampleProject/
├── 📱 SampleProject.xcworkspace
├── 🔧 SampleProject.xcodeproj
│
├── swift-format.json # Swift Format
├── 📦 Main App & Server
│ ├── SampleProject/ # iOS
│ ├── SampleProjectTests/
│ └── SampleProjectServer/ # Server
│
├── 🧩 Modules (Core Libraries)
│
├── 📦 Dependencies
│ └── Packages/ # SPM packages
│
└── 📄 Config Files
├── SampleProject.xcconfig
└── GoogleService-Info.plist

echo "🐥 Formatting"
swift-format format --configuration swift-format.json --ignore-unparsable-files -i -p -r ${PROJECT_DIR}
echo "🐥 Linting"
swift-format lint --configuration swift-format.json --ignore-unparsable-files -p -r ${PROJECT_DIR}

Okay It works

Do you want to use a terminal on your iPad?
I recommend iSH Shell.
You can install packages on your iPad and it works on the offline.

https://apps.apple.com/kr/app/ish-shell/id1436902243?l=en-GB
apk update apk add gcc g++ make musl-dev apk add git apk add vim
apk add zsh curl git sh -c “$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)”

And set zsh as default
ask add shadow passwd chsh -s /bin/zsh
To run chsh, you have to set your password. But on the iSH shell, you haven’t set any password. So please set a new password by running passwd.
And then run chsh -s /bin/zsh
vim hello.cpp
#include <iostream>;
using namespace std;
int main() {
cout << “Hellow from iPad” << endl;
return 0;
}
g++ hello.cpp -o hello ./hello


This post is the second part of our OpenAPI series. Let’s set up GitHub Actions for validating an OpenAPI spec before merging into the main branch.
Check more details
npm i -g ibm-openapi-validator npm i @ibm-cloud/openapi-ruleset
extends: '@ibm-cloud/openapi-ruleset' rules: ibm-accept-and-return-models: info ibm-integer-attributes: false ibm-required-array-properties-in-response: false ibm-property-casing-convention: false
Create an file – validator-rules.yaml
You can check details
ibm rules (inherent from spectral:as rules)
spectral:oas rules
Create an file – validator-config.yml
errorsOnly: true colorizeOutput: true limits: warnings: 25 outputFormat: 'text' summaryOnly: false files: - api.yml ignoreFiles: - validator-config.yml logLevels: root: error ibm-schema-description-exists: debug ruleset: ./validator-rules.yaml produceImpactScore: false markdownReport: true
Check document

lint-openapi -c ./validator-config.yml ./api.yml --errors-only --no-colors
After run the command, It will generate reporting file (md)
node_modules/ package-lock.json package.json api-validator-report.md
name: Validate OpenAPI Documentation
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
validate:
name: Validate OpenAPI Documentation
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install IBM OpenAPI Validator
run: |
npm i -g ibm-openapi-validator
npm i @ibm-cloud/openapi-ruleset
- name: Validate api.yml
id: validation
run: |
if lint-openapi -c ./validator-config.yml ./api.yml --errors-only --no-colors > validation_result.txt 2>&1; then
echo "validation_status=success" >> $GITHUB_OUTPUT
else
echo "validation_status=failed" >> $GITHUB_OUTPUT
fi
continue-on-error: true
- name: Comment PR with validation output
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const validationStatus = '${{ steps.validation.outputs.validation_status }}';
const validationOutput = fs.readFileSync('api-validator-report.md', 'utf8');
let body = '';
if (validationStatus === 'success') {
body = `## ✅ OpenAPI Validation Passed
**File**: \`./api.yml\`
**Commit**: \`${{ github.sha }}\`
${validationOutput}
`;
} else {
body = `## ❌ OpenAPI Validation Failed
**File**: \`./api.yml\`
**Commit**: \`${{ github.sha }}\`
${validationOutput}
`;
}
// Truncate if too long for GitHub comment limit
if (body.length > 65000) {
body = body.slice(0, 65000) + '\n\n... (truncated due to GitHub comment length limit)';
}
// Find existing comment and update or create new one
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const marker = 'OpenAPI Validation';
const existing = comments.find(c => c.body && c.body.includes(marker));
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body
});
}
- name: Fail job if validation failed
if: steps.validation.outputs.validation_status == 'failed'
run: exit 1
Create Github Action yml file
⚠️ Check md file name – api-validator-report.md
const validationOutput = fs.readFileSync('api-validator-report.md', 'utf8');
This is the last step, let’s check the results.

When you create a PR that targets main branch, our GitHub action will run and comment results on your Opened PR.



IBM OpenAPI Validator provides very detailed information. To fix issues, You can update your OpenAPI Spec file or You can change the rulesets if you don’t want to change your OpenAPI Spec files.
BTW This validator is really helpful it prevent wrong OpenAPI spec merged into main branch.
You must be logged in to post a comment.