Import the pebble dev site into devsite/

This commit is contained in:
Katharine Berry 2025-02-17 17:02:33 -08:00
parent 3b92768480
commit 527858cf4c
1359 changed files with 265431 additions and 0 deletions

12
devsite/.env.sample Normal file
View file

@ -0,0 +1,12 @@
URL=http://developer.pebble.com
HTTPS_URL=https://developer.pebble.com
EXTERNAL_SERVER=https://developer-api.getpebble.com
DOCS_URL=
ALGOLIA_APP_ID=
ALGOLIA_API_KEY=
ALGOLIA_SEARCH_KEY=
ALGOLIA_PREFIX=devsite-dev-
GOOGLE_ANALYTICS=
ROLLBAR_CLIENT_TOKEN=
RACK_ENV=development
SKIP_DOCS=false

12
devsite/.gitignore vendored Normal file
View file

@ -0,0 +1,12 @@
__public__
.sass-cache/
.env
tmp/
source/stylesheets/
log/
.bundle/
coverage/
node_modules/
vendor/
.ruby-version
.jekyll-metadata

0
devsite/.rspec Normal file
View file

155
devsite/.scss-lint.yml Normal file
View file

@ -0,0 +1,155 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# This is the scss-lint configuration file for the Pebble Developer Site.
linters:
BorderZero:
enabled: true
CapitalizationInSelector:
enabled: true
ColorKeyword:
enabled: true
Comment:
enabled: true
DebugStatement:
enabled: true
DeclarationOrder:
enabled: true
DuplicateProperty:
enabled: true
ElsePlacement:
enabled: true
style: new_line
EmptyLineBetweenBlocks:
enabled: true
ignore_single_line_blocks: true
EmptyRule:
enabled: true
FinalNewline:
enabled: true
present: true
HexLength:
enabled: true
style: short
HexNotation:
enabled: true
style: lowercase
HexValidation:
enabled: true
IdWithExtraneousSelector:
enabled: true
Indentation:
enabled: true
character: space
width: 2
LeadingZero:
enabled: true
style: include_zero
MergeableSelector:
enabled: true
force_nesting: true
NameFormat:
enabled: true
convention: BEM
PlaceholderInExtend:
enabled: true
PropertySortOrder:
enabled: true
ignore_unspecified: false
PropertySpelling:
enabled: true
extra_properties: []
SelectorDepth:
enabled: true
max_depth: 3
Shorthand:
enabled: true
SingleLinePerProperty:
enabled: true
allow_single_line_rule_sets: true
SingleLinePerSelector:
enabled: true
SpaceAfterComma:
enabled: true
SpaceAfterPropertyColon:
enabled: true
style: one_space
SpaceAfterPropertyName:
enabled: true
SpaceBeforeBrace:
enabled: true
allow_single_line_padding: false
SpaceBetweenParens:
enabled: true
spaces: 0
StringQuotes:
enabled: true
style: single_quotes
TrailingSemicolon:
enabled: true
UnnecessaryMantissa:
enabled: true
UnnecessaryParentReference:
enabled: true
UrlFormat:
enabled: true
UrlQuotes:
enabled: true
ZeroUnit:
enabled: true
Compass::*:
enabled: false
SelectorFormat:
enabled: true
convention: hyphenated_BEM

44
devsite/Gemfile Normal file
View file

@ -0,0 +1,44 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
source 'https://rubygems.org'
ruby '2.2.4'
gem 'slugize', '>= 0.0.3'
gem 'jekyll', '>= 3.0.3'
gem 'jekyll-paginate'
gem 'bundler', '>= 1.7.9'
gem 'rack', '< 1.6.0'
gem 'rack-contrib', '>= 1.2.0'
gem 'nokogiri', '>= 1.6.3.1'
gem 'algoliasearch', '>= 1.6.1'
gem 'htmlentities', '>= 4.3.2'
gem 'rubyzip', '>=1.1.6'
gem 'dotenv', '>= 0.11.1'
gem 'newrelic_rpm', '>= 3.9.8.273'
gem 'rack-wwwhisper', '>= 1.0'
gem 'uglifier', '>= 2.7.0'
gem 'googlestaticmap', '>= 1.2.2'
gem 'rack-rewrite', '>= 1.5.0'
gem 'rack-ssl-enforcer', '>= 0.2.9'
gem 'rack-xframe-options', '>= 0.1.2'
gem 'rack-host-redirect'
gem 'pygments.rb'
gem 'redcarpet'
group :development, :test do
gem 'rspec', '>= 3.1.0'
gem 'simplecov', '>= 0.9.1'
gem 'rubocop', '>= 0.28.0'
end

138
devsite/Gemfile.lock Normal file
View file

@ -0,0 +1,138 @@
GEM
remote: https://rubygems.org/
specs:
addressable (2.3.8)
algoliasearch (1.7.0)
httpclient (~> 2.4)
json (>= 1.5.1)
ast (2.2.0)
colorator (0.1)
diff-lcs (1.2.5)
docile (1.1.5)
dotenv (2.1.0)
execjs (2.6.0)
ffi (1.9.10)
git-version-bump (0.15.1)
googlestaticmap (1.2.2)
htmlentities (4.3.4)
httpclient (2.7.1)
jekyll (3.1.2)
colorator (~> 0.1)
jekyll-sass-converter (~> 1.0)
jekyll-watch (~> 1.1)
kramdown (~> 1.3)
liquid (~> 3.0)
mercenary (~> 0.3.3)
rouge (~> 1.7)
safe_yaml (~> 1.0)
jekyll-paginate (1.1.0)
jekyll-sass-converter (1.4.0)
sass (~> 3.4)
jekyll-watch (1.3.1)
listen (~> 3.0)
json (1.8.3)
kramdown (1.9.0)
liquid (3.0.6)
listen (3.0.6)
rb-fsevent (>= 0.9.3)
rb-inotify (>= 0.9.7)
mercenary (0.3.5)
mini_portile2 (2.0.0)
net-http-persistent (2.9.4)
newrelic_rpm (3.14.2.312)
nokogiri (1.6.7.2)
mini_portile2 (~> 2.0.0.rc2)
parser (2.3.0.2)
ast (~> 2.2)
posix-spawn (0.3.11)
powerpack (0.1.1)
pygments.rb (0.6.3)
posix-spawn (~> 0.3.6)
yajl-ruby (~> 1.2.0)
rack (1.5.5)
rack-contrib (1.4.0)
git-version-bump (~> 0.15)
rack (~> 1.4)
rack-host-redirect (1.2.1)
rack
rack-rewrite (1.5.1)
rack-ssl-enforcer (0.2.9)
rack-wwwhisper (1.1.9)
addressable (~> 2.0)
net-http-persistent
rack (~> 1.0)
rack-xframe-options (0.1.2)
rack (>= 0.9.1)
rainbow (2.1.0)
rb-fsevent (0.9.7)
rb-inotify (0.9.7)
ffi (>= 0.5.0)
redcarpet (3.3.4)
rouge (1.10.1)
rspec (3.4.0)
rspec-core (~> 3.4.0)
rspec-expectations (~> 3.4.0)
rspec-mocks (~> 3.4.0)
rspec-core (3.4.2)
rspec-support (~> 3.4.0)
rspec-expectations (3.4.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.4.0)
rspec-mocks (3.4.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.4.0)
rspec-support (3.4.1)
rubocop (0.36.0)
parser (>= 2.3.0.0, < 3.0)
powerpack (~> 0.1)
rainbow (>= 1.99.1, < 3.0)
ruby-progressbar (~> 1.7)
ruby-progressbar (1.7.5)
rubyzip (1.1.7)
safe_yaml (1.0.4)
sass (3.4.21)
simplecov (0.11.1)
docile (~> 1.1.0)
json (~> 1.8)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.0)
slugize (0.0.3)
uglifier (2.7.2)
execjs (>= 0.3.0)
json (>= 1.8.0)
yajl-ruby (1.2.1)
PLATFORMS
ruby
DEPENDENCIES
algoliasearch (>= 1.6.1)
bundler (>= 1.7.9)
dotenv (>= 0.11.1)
googlestaticmap (>= 1.2.2)
htmlentities (>= 4.3.2)
jekyll (>= 3.0.3)
jekyll-paginate
newrelic_rpm (>= 3.9.8.273)
nokogiri (>= 1.6.3.1)
pygments.rb
rack (< 1.6.0)
rack-contrib (>= 1.2.0)
rack-host-redirect
rack-rewrite (>= 1.5.0)
rack-ssl-enforcer (>= 0.2.9)
rack-wwwhisper (>= 1.0)
rack-xframe-options (>= 0.1.2)
redcarpet
rspec (>= 3.1.0)
rubocop (>= 0.28.0)
rubyzip (>= 1.1.6)
simplecov (>= 0.9.1)
slugize (>= 0.0.3)
uglifier (>= 2.7.0)
RUBY VERSION
ruby 2.2.4p230
BUNDLED WITH
1.13.6

184
devsite/README.md Normal file
View file

@ -0,0 +1,184 @@
# [developer.pebble.com][site]
[![Build Status](https://magnum.travis-ci.com/pebble/developer.getpebble.com.svg?token=HUQ9CCUxB447Nq1exrnd)][travis]
This is the repository for the [Pebble Developer website][site].
The website is built using [Jekyll](http://jekyllrb.com) with some plugins that
provide custom functionality.
For anyone who wants to contribute to the content of the site, you should find
the information in one of the sections below.
* [Blog Posts](#blog-posts)
* [Markdown](#markdown)
* [Landing Page Features](#landing-page-features)
* [Colors](#colors)
## Getting Started
Once you have cloned the project you will need to run `bundle install` to
install the Ruby dependencies. If you do not have [bundler](http://bundler.io/)
installed you will need to run `[sudo] gem install bundler` first.
You should also do `cp .env.sample .env` and edit the newly created `.env` file
with the appropriate values. Take a look at the
[Environment Variables documentation](/docs/environment.md) for more details.
To start the Jekyll web server, run `bundle exec jekyll serve`.
## JS Documentation
The PebbleKit JS and Rocky documentation is generated with the
[documentation.js](documentation.js.org) framework. The documentation tool can
create a JSON file from the JSDocs contained in the [js-docs](/js-docs)
folder.
To install documentation.js, run `npm install -g documentation`
To regenerate the `/source/_data/rocky-js.json` file, run `./scripts/generate-rocky-docs.sh`
> **NOTE**: This is intended to be a temporary hack. Ideally the rocky-js.json
> file is generated as part of the release generator (and built using the actual
> Rocky.js source, or stubs in the Tintin repository.
## Blog Posts
### Setting up a new author
Add your name to the `source/_data/authors.yml` so the blog knows who you are!
```
blogUsername:
name: First Last
photo: https://example.com/you.png
```
### Creating a new blog post
Add a Markdown file in `source/_posts/` with a filename in following the
format: `YYYY-MM-DD-Title-of-the-blog-most.md`.
Start the file with a block of YAML metadata:
```
---
title: Parlez-vous Pebble? Sprechen sie Pebble? ¿Hablas Pebble?
author: blogUsername
tags:
- Freshly Baked
---
```
You should pick one tag from this list:
* Freshly Baked - Posts announcing or talking about new features
* Beautiful Code - Posts about writing better code
* "#makeawesomehappen" - Hackathons/events/etc
* At the Pub - Guest Blog Posts (presumably written at a pub)
* Down the Rabbit Hole - How Pebble works 'on the inside'
* CloudPebble - Posts about CloudPebble
* Timeline - Posts about Timeline
### Setting the post's preview text
The blog's homepage will automatically generate a 'preview' of your blog post. It does this by finding the first set of 3 consecutive blank lines, and using everything before those lines as the preview.
You should aim to have your preview be 1-2 paragraphs, and end with a hook that causes the reader to want to click the 'Read More' link.
## Markdown
There is a [Markdown styleguide and additional syntax cheatsheat][markdown]
you should use if you are writing any Markdown for the site. That includes all
blog posts and guides.
## Landing Page Features
The landing page of the website contains a slideshow (powered by [slick][slick]).
The contents of the slideshow, or 'features' as we call them, are generated
from the features data file found at `source/_data/features.yaml`.
There are two main types of features, images and videos.
### Image Feature
```yaml
- title: Want to make your apps more internationally friendly?
url: /guides/publishing-tools/i18n-guide/
background_image: /images/landing-page/i18n-guide.png
button_text: Read our brand new guide to find out how
button_fg: black
button_bg: yellow
duration: 5000
```
It should be relatively clear what each of the fields is for. For the
`button_fg` and `button_bg` options, check out the [colors](#colors) section
for the available choices.
The `background_image` can either be a local asset file or an image on an
external web server.
**Please Remember:** The landing page will see a lot of traffic so you
should strive to keep image sizes small, while still maintaing relatively large
dimensions. Run the images through minifying tools, such as
[TinyPNG][tinypng] or [TinyJPG][tinyjpg], before commiting them to the site.
### Video Feature
```yaml
- title: Send a Smile with Android Actionable Notifications
url: /blog/2014/12/19/Leverage-Android-Actionable-Notifications/
background_image: /images/landing-page/actionable-notifications.png
video:
url: https://s3.amazonaws.com/developer.getpebble.com/videos/actionable-notifications.mp4
button_text: Learn how to supercharge Your Android Apps
button_fg: white
button_bg: green
duration: 5000
```
To prevent massively bloating the size of this repository, we are hosting all
videos externally on S3. If you do not have permission to upload videos to our
S3 bucket, you will need to ask someone who does!
In order to enable to videos to play across all of the browsers + platforms,
you will need to provided the video in MP4, OGV and WEBM formats.
There is a script provided in the scripts folder to do the automatic conversion
from MP4, and to export the first frame of the video as a PNG used as a
placeholder while the video loads.
```sh
./scripts/video-encode.sh PATH_TO_MP4
```
If you run the script as above, it will create an OGV, WEBM and PNG file in the same folder as the MP4. The PNG file should go in the `/assets/images/landing-page/` folder, and the three video files should be uploaded to S3.
## Colors
Buttons and Alerts come are available in several different color options, with
both foreground and background modifier classes to give you maximum control.
The available colors:
* white
* green
* blue
* red
* purple
* yellow
* orange
* lightblue
* dark-red
To set the background, use `--bg-<COLOR>` modifier. To set the foreground (i.e)
the text color, use `--fg-<COLOR>`.
## Troubleshooting
Trouble building the developer site? Read the [Troubleshooting](/docs/troubleshooting.md) page for some possible solutions.
[site]: https://developer.pebble.com
[markdown]: ./docs/markdown.md
[slick]: http://kenwheeler.github.io/slick/
[tinypng]: https://tinypng.com/
[tinyjpg]: https://tinyjpg.com/
[travis]: https://magnum.travis-ci.com/pebble/developer.getpebble.com

28
devsite/Rakefile Normal file
View file

@ -0,0 +1,28 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
task default: "assets:precompile"
namespace :assets do
desc "Precompile assets"
task :precompile do
Rake::Task["clean"].invoke
sh "bundle exec jekyll build --trace"
end
end
desc "Remove compiled files"
task :clean do
sh "rm -rf #{File.dirname(__FILE__)}/__public__/*"
end

111
devsite/_config.yml Normal file
View file

@ -0,0 +1,111 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
url: https://developer.pebble.com
https_url: https://developer.pebble.com
baseurl:
asset_path: /assets
external_server: https://developer-api.getpebble.com
title: Pebble Developers
description: The official developer website for the Pebble smartwatch.
source: source/
destination: __public__/
plugins_dir: plugins/
debug: true
gems: [jekyll-paginate]
# Blog Options
permalink: none
paginate: 8
paginate_path: "blog/:num"
excerpt_separator: "\n\n\n"
future: true
disqus:
short_name: pebbletechblog
show_comment_count: true
# Markdown Options
markdown: PebbleMarkdownParser
markdown_ext: md
# SASS options.
sass:
sass_dir: _sass
style: :compressed
# Helpful and easily changeable external links.
links:
pebble: https://www.pebble.com
jobs: https://www.pebble.com/jobs/
twitter: https://twitter.com/pebbledev/
cloudpebble: https://cloudpebble.net/
cloudpebble_beta: https://beta.cloudpebble.net/
devportal: https://dev-portal.getpebble.com/
site_repo: https://github.com/pebble/developer.getpebble.com/
community_resources_repo: https://github.com/pebble/community-resources/
github: https://github.com/pebble/
forums: https://forums.pebble.com
forums_developer: https://forums.pebble.com/c/development
pebblekit_android: https://github.com/pebble/pebble-android-sdk/
pebblekit_ios: https://github.com/pebble/pebble-ios-sdk/
examples_org: https://github.com/pebble-examples
pebblekit_android_jar: https://oss.sonatype.org/service/local/repositories/releases/content/com/getpebble/pebblekit/3.0.0/pebblekit-3.0.0-eclipse.jar
legal:
privacy: https://www.pebble.com/legal/privacy/
cookies: https://www.pebble.com/legal/cookies/
s3_assets: https://developer-assets.getpebble.com
pebble_tool_root: https://s3.amazonaws.com/assets.getpebble.com/pebble-tool/
libpebble: https://github.com/pebble/libpebble2
kickstarter3: https://www.kickstarter.com/projects/597507018/pebble-2-time-2-and-core-an-entirely-new-3g-ultra
discord_invite: http://discord.gg/aRUAYFN
# Jekyll collections.
collections:
guides:
output: true
permalink: /guides/:path/
changelogs:
output: true
permalink: /sdk/changelogs/:path/
# Default options based for various scopes
defaults:
- scope:
path: ""
type: "posts"
values:
layout: "blog/post"
generate_toc: true
permalink: /blog/:year/:month/:day/:title/
- scope:
path: ""
type: "guides"
values:
layout: guides/default
menu: true
generate_toc: true
guide_group:
guide_subgroup:
menu_section: guides
- scope:
path: ""
type: changelogs
values:
layout: sdk/changelog
generate_toc: true

28
devsite/app.json Normal file
View file

@ -0,0 +1,28 @@
{
"name": "Pebble Developer Website",
"description": "The official Pebble Developer website.",
"website": "https://developer.pebble.com",
"repository": "https://github.com/pebble/developer.getpebble.com",
"env": {
"EXTERNAL_SERVER": {
"required": true
},
"DOCS_URL": {
"required": true
},
"ALGOLIA_APP_ID": {
"required": true
},
"ALGOLIA_API_KEY": {
"required": true
},
"ALGOLIA_SEARCH_KEY": {
"required": true
},
"ALGOLIA_PREFIX": "devsite-staging-",
"RACK_ENV": "staging",
"HEROKU_APP_NAME": {
"required": true
}
}
}

53
devsite/config.ru Normal file
View file

@ -0,0 +1,53 @@
require 'newrelic_rpm'
require 'rack/contrib/try_static'
require 'rack/rewrite'
require 'dotenv'
require 'rack/ssl-enforcer'
require 'rack/xframe-options'
require 'rack-host-redirect'
Dotenv.load
require 'dotenv'
def load_404
not_found_page = File.expand_path('../__public__/404.html', __FILE__)
File.read(not_found_page)
end
if ENV['RACK_ENV'] == 'development'
def not_found_html
load_404
end
else
use Rack::SslEnforcer,
:hsts => { :expires => 500, :subdomains => false },
:strict => true
not_found_html = load_404
end
use Rack::XFrameOptions, 'DENY'
# Determine if the C preview docs are enabled
docs_config = YAML.load_file('source/_data/docs.yaml')
preview_docs = docs_config.key?('c_preview')
use Rack::Rewrite do
# Redirect all old PebbleKit docs to their new location
r301 %r{/docs/js/(.*)}, '/docs/pebblekit-js/$1'
# Redirect C preview docs to main C docs if there are no preview docs
r302 %r{/docs/c/preview/?(.*)}, '/docs/c/$1' unless preview_docs
end
use Rack::HostRedirect, {
'developer.getpebble.com' => 'developer.pebble.com'
}
use Rack::TryStatic,
root: '__public__',
urls: %w(/),
try: %w(.html index.html /index.html)
run lambda{ |_env|
[404, { 'Content-Type' => 'text/html' }, [not_found_html]]
}

4971
devsite/docs.json Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,12 @@
# Pebble Developer Site &middot; Development Guide
## Handlebars Templates
In order to reduce the size of the JS of the site, we are now pre-compiling
the Handlebars templates and just using the runtime Handlebars library.
If you change, add or remove from the templates, you just recompile them
into the file at `/source/assets/js/templates.js`.
There is a bash script at `/scripts/update-templates.sh` that you can use to
generate this file.

View file

@ -0,0 +1,84 @@
# Environment Variables
The following environment variables are used in the generation of the site.
## URL
This overrides the `url` configuration parameter of Jeyll. Set this to the root
of where the site will be hosted.
```
URL=http://developer.pebble.com
```
## HTTPS_URL
This overrides the `https_url` configuration parameter of Jeyll. Set this to
the secure root of where the site will be hosted.
```
HTTPS_URL=https://developer.pebble.com
```
## PORT
The port on which the Jekyll server will listen. If you don't set this it will
default to port `4000`.
```
PORT=8000
```
## ASSET_PATH
This sets the `asset_path` configuration variable, which tells the site where
the assets are to be found.
During development and testing, this can be set to the relative URL of the
assets folder inside the main site.
For production, this should be set to the CDN where the assets will be uploaded.
*Note:* As of 8th January 2014, the production version of the site still used
local assets and not a CDN.
```
ASSET_PATH=assets/
```
## EXTERNAL_SERVER
This sets the `external_server` configuration variable, which tells the site the
location of the external server used for events, community blog and contact.
```
EXTERNAL_SERVER=https://developer-api.getpebble.com
```
## DOCS_URL
The URL of the server on which the documentation sources are being built.
The production and staging values are private, so if you do not work for Pebble
you will have to omit it from the environment (or `.env` file). Sorry
## ALGOLIA_*
The site search is powered by [Algolia](https://algolia.com). There are four
environment variables that are required to turn on indexing at build time and
also correctly setup the client JS for searching. The production and staging
values can be found on our Algolia account.
If you do not work for Pebble, or don't care about testing the indexing, then
omit these values from the environment (or `.env` file) to disable Algolia.
The `ALGOLIA_PREFIX` value is extremely important. Make sure you set it if you
are enabling Algolia support on the site, and check that it matches the scoped
search key.
```
ALGOLIA_APP_ID=
ALGOLIA_API_KEY=
ALGOLIA_SEARCH_KEY=
ALGOLIA_PREFIX=
```

305
devsite/docs/markdown.md Normal file
View file

@ -0,0 +1,305 @@
# Writing Markdown
If you are writing anything in Markdown for the Pebble Developer site, you
should read this guide to learn about some of the rules and enhancements that
the site has, beyond those of "standard Markdown".
## Styleguide
### 80 character lines
To keep your Markdown files readable and easy to review, please break all lines
at 80 characters.
*NOTE:* The line breaking does not affect the HTML output, this is purely to
keep the source files readable and reviewable.
### Headers
Use the `#`, `##` etc syntax for headers, and include a space after the hashes
and before the header text.
```
## Write Headers Like This
##Don't Write Them Like This
And Definitely Don't Do This
=======
```
You should also generally avoid using the top level header (`#`) because the
page that is displaying the content will be using the document title in a \<h1\>
tag automatically.
#### Table of Contents
If enabled, the table of contents for the document will include all headers on
the page.
You can enable/disable table of contents generation for a specific page in the
YAML metadata:
```
generate_toc: true
```
#### Anchors
All headers automatically have anchors attached to them, so you can easily link
to sections of the page. The ID for the header will be the slugized header text.
For example, `## Install Your App` will become `#install-your-app`.
### Blockcode
Use triple backticks for block code, and
[specify the language](http://pygments.org/languages/) to ensure the syntax is
highlighted correctly.
```js
var foo = 'bar';
```
#### Click to Copy
By default, all code blocks will include the Click to Copy button in the
top right corner. If you want to disable it, prepend the language with `nc^`.
```nc^text
This is not for copying!
```
### Images
In blog posts and guides, images will be block elements that are centered on
the page. *Plan accordingly.*
#### Size
You can control the width (and optionally height) of images using the following
syntax:
```
![Image with width](/images/huge_image.png =300)
![Image with width and height](/images/huge_image.png =300x400)
```
### HTML
Do not use HTML unless you **absolutely** have to. It is almost always better to
use Markdown so that we can more easily maintain a consistent style across the
site.
## Additional Syntax
### Buttons
To convert any link into a button simply append a `>` onto the end of the text.
```
[Button Text >](http://google.com/)
```
You can optionally pass extra button classes after the `>` to modify the style
of the button.
```
[Wide Orange Button >{wide,fg-orange}](http://google.com)
```
The available classes are:
* wide
* small
* center
* fg-COLOR
* bg-COLOR
*Where COLOR is any one of the [available colors](README.md#colors).*
### Link Data
To add additional data attributes to links (useful for outbound tracking),
append a `>` to the end of the link title, and format the content as below.
```
[Link Text](http://google.com "Link Title >{track-event:click,track-name:google}")
```
This will create a link with the attributes `data-track-event="click"` and
`data-track-name="google"`.
### SDK Documentation Links
If you wish to link to a section of the SDK documentation, you can do so using
double backticks. This can either be done to enhance existing inline code
or in the text of a link.
```
This will link to the ``window_create`` documentation.
You should check out the page on [Events](``event services``)
```
### Pebble Screenshots
If you want to provide a watch screenshot and have it displayed in a Pebble
wrapper, you should upload the 144x168 image and use the following syntax.
```
![ >{pebble-screenshot,pebble-screenshot--time-red}](/images/screenshot.png)
```
You can pick from any of the following screenshot wrappers:
* pebble-screenshot--black
* pebble-screenshot--white
* pebble-screenshot--red
* pebble-screenshot--gray
* pebble-screenshot--orange
* pebble-screenshot--steel-black
* pebble-screenshot--steel-stainless
* pebble-screenshot--snowy-black
* pebble-screenshot--snowy-red
* pebble-screenshot--snowy-white
* pebble-screenshot--time-round-black-20
* pebble-screenshot--time-round-red-14
The following screenshot classes exist, but the accompanying images are not
currently available. They will be aliased to black-20 or red-14 as size
dictates:
* pebble-screenshot--time-round-rosegold-14
* pebble-screenshot--time-round-silver-14
* pebble-screenshot--time-round-silver-20
> Please match the wrapper to the screenshot where possible. For example, do not
use an original Pebble wrapper with a color screenshot.
#### Screenshot Viewer
If you want to show matching screenshots across multiple platforms, you should use the new
`screenshot_viewer` tag.
Here is an example of it in use:
```
{% screenshot_viewer %}
{
"image": "/images/guides/pebble-apps/display-animations/submenu.png",
"platforms": [
{ "hw": "basalt", "wrapper": "time-red" },
{ "hw": "chalk", "wrapper": "time-round-red-14" }
]
}
{% endscreenshot_viewer %}
```
The URL to the image gets the hardware platform insert into it, so in order to make
the above example work, you should have two files with the following names:
```
/source/assets/images/guides/pebble-apps/display-animations/submenu~basalt.png
/source/assets/images/guides/pebble-apps/display-animations/submenu~chalk.png
```
### Alerts
Some information requires more prominent formatting than standard block notes.
Use the `alert` Liquid tag for this purpose. Both 'notice' (purple) and
'important' (dark red) are supported for appropriate levels of highlighting.
Some examples are below:
```
{% alert important %}
PebbleKit JS and PebbleKit Android/iOS may **not** be used in conjunction.
{% endalert %}
```
```
{% alert notice %}
This API is currently in the beta stage, and may be changed before final
release.
{% endalert %}
```
### SDK Platform Specific Paragraphs
On pages that have the SDK Platform choice system, you can tag paragraphs as
being only relevant for CloudPebble or local SDK users. Text, code snippets,
images, and other markdown are all supported.
First, add `platform_choice: true` to the page YAML metadata.
Specify platform-specific sections of markdown using the `platform` Liquid tag:
```
{% platform local %}
Add the resource to your project in `package.json`.
{% endplatform %}
{% platform cloudpebble %}
Add the resource to your project by clicking 'Add New' next to 'Resources' in
the project sidebar.
{% endplatform %}
```
### Formatting
The following additional text formatting syntax is supported.
#### Strikethrough
```
This is some ~~terribly bad~~ amazingly good code.
```
#### Highlight
```
CloudPebble is ==extremely== good.
```
#### Tables
Tables are supported with the
[PHP Markdown syntax](https://michelf.ca/projects/php-markdown/extra/#table).
```
| First Header | Second Header |
| ------------- | ------------- |
| Content Cell | Content Cell |
| Content Cell | Content Cell |
```
### Emoji
You can use emoji in your text by using the colon syntax.
```
If you're a beginner Pebble developer, you should use :cloud:Pebble
```
### Embedded Content
#### YouTube
To embed a YouTube video or playlist, use the standard link syntax with EMBED
as the link title.
```
You should check out this video on developing Pebble apps:
[EMBED](https://www.youtube.com/watch?v=LU_hPBhgjGQ)
```
#### Gist
To embed a GitHub Gist, use the standard link syntax with EMBED as the link
title.
```
Here is the Gist code.
[EMBED](https://gist.github.com/JaviSoto/5405969)
```

View file

@ -0,0 +1,16 @@
# Troubleshooting
This page contains fixes to known problems encountered from building the
developer site, and how they were fixed. This may help you if you have
the same problems.
## Nokogiri
**Error**
> An error occurred while installing nokogiri (1.6.7.2), and Bundler cannot continue.
> Make sure that `gem install nokogiri -v '1.6.7.2'` succeeds before bundling.
**Solution**
`gem install nokogiri -- --use-system-libraries`

View file

@ -0,0 +1,428 @@
/**
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @namespace Pebble
*
* @desc The Pebble namespace is where all of the Pebble specific methods and
* properties exist. This class contains methods belonging to PebbleKit JS and
* allows bi-directional communication with a C or JavaScript watchapp, as well as managing
* the user's timeline subscriptions, creating AppGlance slices and obtaining
* information about the currently connected watch.
*/
var Pebble = new Object;
/**
* @desc Adds a listener for PebbleKit JS events, such as when an ``AppMessage`` is
* received or the configuration view is opened or closed.
*
* #### Event Type Options
*
* Possible values:
*
* * `ready` - The watchapp has been launched and the PebbleKit JS component
* is now ready to receive events.
* * `appmessage` - The watch sent an ``AppMessage`` to PebbleKit JS. The
* AppMessage ``Dictionary`` is contained in the payload property (i.e:
* `event.payload`). The payload consists of key-value pairs, where the keys
* are strings containing integers (e.g: "0"), or aliases for keys defined
* in package.json (e.g: "KEY_EXAMPLE"). Values should be integers, strings
* or byte arrays (arrays of characters). This event is not available to
* {@link /docs/rockyjs/ Rocky.js} applications, and attempting to register it will throw an exception.
* * `showConfiguration` - The user has requested the app's configuration
* webview to be displayed. This can occur either upon the app's initial
* install or when the user taps 'Settings' in the 'My Pebble' view within
* the phone app.
* * `webviewclosed` - The configuration webview was closed by the user. If
* the webview had a response, it will be contained in the response property
* (i.e: `event.response`). This response can be used to feed back user
* preferences to the watchapp.
* * `message` - Provide a {@link #PostMessageCallback PostMessageCallback}
* as the callback. The message event is emitted every time PebbleKit JS
* receives a {@link #postMessage postMessage} from the {@link /docs/rockyjs/ Rocky.js}
* application. The payload contains a simple JavaScript object. (i.e. `event.data`).
* This event type can only be used with {@link /docs/rockyjs/ Rocky.js} applications.
* * `postmessageconnected` - Provide a {@link #PostMessageConnectedCallback PostMessageConnectedCallback}
* as the callback. The event may be emitted immediately upon subscription,
* if the subsystem is already connected. It is also emitted when connectivity is established.
* This event type can only be used with {@link /docs/rockyjs/ Rocky.js} applications.
* * `postmessagedisconnected` - Provide a {@link #PostMessageDisconnectedCallback PostMessageDisconnectedCallback}
* as the callback. The event may be emitted immediately upon subscription,
* if the subsystem is already disconnected. It is also emitted when connectivity is lost.
* This event type can only be used with {@link /docs/rockyjs/ Rocky.js} applications.
* * `postmessageerror` - Provide a {@link #PostMessageErrorCallback PostMessageErrorCallback}
* as the callback. The event is emitted when a transmission error occurrs.
* Your message has not been delivered. The type of error is not provided.
* This event type can only be used with {@link /docs/rockyjs/ Rocky.js} applications.
*
* @param {String} type - The type of the event, from the list described above.
* @param {EventCallback} callback - The developer defined {@link #EventCallback EventCallback}
* to receive any events of the type specified that occur.
*/
Pebble.addEventListener = function(type, callback) { };
/**
* @desc Attaches an event handler to the specified events. Synonymous with
[Pebble.addEventListener()](#addEventListener). Only applicable to
{@link /docs/rockyjs/ Rocky.js} applications.
*
* `Pebble.on(type, callback);`
*
* @param {String} type - The type of the event, from the list described above.
* @param {EventCallback} callback - The developer defined {@link #EventCallback EventCallback}
* to receive any events of the type specified that occur.
*/
Pebble.on = function(type, callback) { };
/**
* @desc Remove an existing event listener previously registered with
* [Pebble.addEventListener()](#addEventListener) or [Pebble.on()](#on).
*
* @param {String} type - The type of the event listener to be removed. See
* [Pebble.addEventListener()](#addEventListener) for a list of available event types.
* @param {Function} callback - The existing developer-defined function that was
* previously registered.
*/
Pebble.removeEventListener = function(type, callback) { };
/**
* @desc Remove an existing event handler from the specified events. Synonymous
* with [Pebble.removeEventListener()](#removeEventListener). Only applicable to
* {@link /docs/rockyjs/ Rocky.js} applications.
*
* `Pebble.off(type, callback);`
*
* @param {String} type - The type of the event listener to be removed. See
* [Pebble.addEventListener()](#addEventListener) for a list of available types.
* @param {Function} callback - The existing developer-defined function that was
* previously registered.
*/
Pebble.off = function(type, callback) { };
/**
* @desc Show a simple modal notification on the connected watch.
*
* @param {String} title - The title of the notification
* @param {String} body - The main content of the notification
*/
Pebble.showSimpleNotificationOnPebble = function(title, body) { };
/**
* @desc Send an AppMessage to the app running on the watch. Messages should be
* in the form of JSON objects containing key-value pairs. See
* Pebble.sendAppMessage() for valid key and value data types.
* `Pebble.sendAppMessage = function(data, onSuccess, onFailure) { };`
* Please note that `sendAppMessage` is `undefined` in
* {@link /docs/rockyjs/ Rocky.js} applications, see {@link #postMessage postMessage} instead.
*
* @returns {Number} The transaction id for this message
*
* @param {Object} data - A JSON object containing key-value pairs to send to
* the watch. Values in arrays that are greater then 255 will be mod 255
* before sending.
* @param {AppMessageAckCallback} onSuccess - A developer-defined {@link #AppMessageAckCallback AppMessageAckCallback}
* callback to run if the watch acknowledges (ACK) this message.
* @param {AppMessageOnFailure} onFailure - A developer-defined {@link #AppMessageNackCallback AppMessageNackCallback}
* callback to run if the watch does NOT acknowledge (NACK) this message.
*/
Pebble.sendAppMessage = function(data, onSuccess, onFailure) { };
/**
* @desc Sends a message to the {@link /docs/rockyjs/ Rocky.js} component. Please be aware
* that messages should be kept concise. Each message is queued, so
* `postMessage()` can be called multiple times immediately. If there is a momentary loss of connectivity, queued
* messages may still be delivered, or automatically removed from the queue
* after a few seconds of failed connectivity. Any transmission failures, or
* out of memory errors will be raised via the `postmessageerror` event.
*
* `Pebble.postMessage({temperature: 30, conditions: 'Sunny'});`
*
* @param {Object} data - A {@link #PostMessageCallback PostMessageCallback} containing
* the data to deliver to the watch.
* This will be received in the `data` field of the `type` delivered to
* the `on('message', ...)` handler.
*/
Pebble.postMessage = function(data) { };
/**
* @desc Get the user's timeline token for this app. This is a string and is
* unique per user per app. Note: In order for timeline tokens to be
* available, the app must be submitted to the Pebble appstore, but does not
* need to be public. Read more in the
* {@link /guides/pebble-timeline/timeline-js/ timeline guides}.
*
* @param {TimelineTokenCallback} onSuccess - A developer-defined {@link #TimelineTokenCallback TimelineTokenCallback}
* callback to handle a successful attempt to get the timeline token.
* @param {Function} onFailure - A developer-defined callback to handle a
* failed attempt to get the timeline token.
*/
Pebble.getTimelineToken = function(onSuccess, onFailure) { };
/**
* @desc Subscribe the user to a timeline topic for your app. This can be used
* to filter the different pins a user could receive according to their
* preferences, as well as maintain groups of users.
*
* @param {String} topic - The desired topic to be subscribed to. Users will
* receive all pins pushed to this topic.
* @param {Function} onSuccess - A developer-defined callback to handle a
* successful subscription attempt.
* @param {Function} onFailure - A developer-defined callback to handle a
* failed subscription attempt.
*/
Pebble.timelineSubscribe = function(topic, onSuccess, onFailure) { };
/**
* @desc Unsubscribe the user from a timeline topic for your app. Once
* unsubscribed, the user will no longer receive any pins pushed to this
* topic.
*
* @param {String} topic - The desired topic to be unsubscribed from.
* @param {Function} onSuccess - A developer-defined callback to handle a
* successful unsubscription attempt.
* @param {Function} onFailure - A developer-defined callback to handle a
* failed unsubscription attempt.
*/
Pebble.timelineUnsubscribe = function(topic, onSuccess, onFailure) { };
/**
* @desc Obtain a list of topics that the user is currently subscribed to. The
* length of the list should be checked to determine whether the user is
* subscribed to at least one topic.
*
* `Pebble.timelineSubscriptions(function(topics) { console.log(topics); }, function() { console.log('error'); } );`
*
* @param {TimelineTopicsCallback} onSuccess - The developer-defined function to process the
* retuned list of topic strings.
* @param {Function} onFailure - The developer-defined function to gracefully
* handle any errors in obtaining the user's subscriptions.
*/
Pebble.timelineSubscriptions = function(onSuccess, onFailure) { };
/**
* @desc Obtain an object containing information on the currently connected
* Pebble smartwatch.
*
* **Note:** This function is only available when using the Pebble Time
* smartphone app. Check out our guide on {@link /guides/communication/using-pebblekit-js Getting Watch Information}
* for details on how to use this function.
*
* @returns {WatchInfo} A {@link #WatchInfo WatchInfo} object detailing the
* currently connected Pebble watch.
*/
Pebble.getActiveWatchInfo = function() { };
/**
* @desc Returns a unique account token that is associated with the Pebble
* account of the current user.
*
* **Note:** The behavior of this changed slightly in SDK 3.0. Read the
* {@link /guides/migration/migration-guide-3/ Migration Guide} to learn the
* details and how to adapt older tokens.
*
* @returns {String} A string that is guaranteed to be identical across devices
* if the user owns several Pebble or several mobile devices. From the
* developer's perspective, the account token of a user is identical across
* platforms and across all the developer's watchapps. If the user is not
* logged in, this function will return an empty string ('').
*/
Pebble.getAccountToken = function() { };
/**
* @desc Returns a a unique token that can be used to identify a Pebble device.
*
* @returns {String} A string that is is guaranteed to be identical for each
* Pebble device for the same app across different mobile devices. The token
* is unique to your app and cannot be used to track Pebble devices across
* applications.
*/
Pebble.getWatchToken = function() { };
/**
* @desc Triggers a reload of the app glance which first clears any existing
* slices and then adds the provided slices.
*
* @param {AppGlanceSlice} appGlanceSlices - {@link #AppGlanceSlice AppGlanceSlice}
* JSON objects to add to the app glance.
* @param {AppGlanceReloadSuccessCallback} onSuccess - The developer-defined
* callback which is called if the reload operation succeeds.
* @param {AppGlanceReloadFailureCallback} onFailure - The developer-defined
* callback which is called if the reload operation fails.
*/
Pebble.appGlanceReload = function(appGlanceSlices, onSuccess, onFailure) { };
/**
* @desc When an app is marked as configurable, the PebbleKit JS component must
* implement `Pebble.openURL()` in the `showConfiguration` event handler. The
* Pebble mobile app will launch the supplied URL to allow the user to configure
* the watchapp or watchface. See the
* {@link /guides/user-interfaces/app-configuration-static/ App Configuration guide}.
*
* @param {String} url - The URL of the static configuration page.
*/
Pebble.openURL = function(url) { };
/**
* @typedef {Function} AppGlanceReloadSuccessCallback
* @memberof Pebble
*
* @desc Called when AppGlanceReload is successful.
* @param {AppGlanceSlice} AppGlanceSlices - An {@link #AppGlanceSlice AppGlanceSlice} object
* containing the app glance slices.
*/
/**
* @typedef {Function} AppGlanceReloadFailureCallback
* @memberof Pebble
*
* @desc Called when AppGlanceReload has failed.
* @param {AppGlanceSlice} AppGlanceSlices - An {@link #AppGlanceSlice AppGlanceSlice} object
* containing the app glance slices.
*/
/**
* @typedef {Function} AppMessageAckCallback
* @memberof Pebble
*
* @desc Called when an AppMessage is acknowledged by the watch.
* @param {Object} data - An object containing the callback data. This contains
* the `transactionId` which is the transaction ID of the message.
*/
/**
* @typedef {Function} AppMessageNackCallback
* @memberof Pebble
*
* @desc Called when an AppMessage is not acknowledged by the watch.
* @param {Object} data - An object containing the callback data. This contains
* the `transactionId` which is the transaction ID of the message
* @param {String} error - The error message
*/
/**
* @typedef {Function} EventCallback
* @memberof Pebble
*
* @desc Called when an event of any type previously registered occurs. The
* parameters are different depending on the type of event, shown in
* brackets for each parameter listed here.
* @param {Object} event - An object containing the event information, including:
* * `type` - The type of event fired, from the list in the description of [Pebble.addEventListener()](#addEventListener).
* * `payload` - The dictionary sent over ``AppMessage`` consisting of
* key-value pairs. *This field only exists for `appmessage` events.*
* * `response` - The contents of the URL navigated to when the
* configuration page was closed, after the anchor. This may be encoded,
* which will require use of decodeURIComponent() before reading as an
* object. *This field only exists for for `webviewclosed` events.*
*/
/**
* @typedef {Function} TimelineTokenCallback
* @memberof Pebble
*
* @desc Called when the user's timeline token is available.
* @param {String} token - The user's token.
*/
/**
* @typedef {Function} TimelineTopicsCallback
* @memberof Pebble
*
* @desc Called when the user's list of subscriptions is available for processing by the developer.
* @param {[String]} List of topic strings that the user is subscribed to
*/
/**
* @typedef {Function} PostMessageCallback
* @memberof Pebble
*
* @desc The callback function signature to be used with the `message`
* {@link #on event}.
*
* @param {Object} event - An object containing information about the event:
* * `type` - The type of event which was triggered.
* * `data` - The data sent within the message.
*/
/**
* @typedef {Function} PostMessageErrorCallback
* @memberof Pebble
*
* @desc The callback function signature to be used with the `postmessageerror`
* {@link #on event}.
*
* @param {Object} event - An object containing information about the event:
* * `type` - The type of event which was triggered.
* * `data` - The data failed to send within the message.
*/
/**
* @typedef {Function} PostMessageConnectedCallback
* @memberof Pebble
*
* @desc The callback function signature to be used with the `postmessageconnected`
* {@link #on event}.
*
* @param {Object} event - An object containing information about the event:
* * `type` - The type of event which was triggered.
*/
/**
* @typedef {Function} PostMessageDisconnectedCallback
* @memberof Pebble
*
* @desc The callback function signature to be used with the `postmessagedisconnected`
* {@link #on event}.
*
* @param {Object} event - An object containing information about the event:
* * `type` - The type of event which was triggered.
*/
/**
* @typedef {Object} WatchInfo
* @memberof Pebble
*
* @desc Provides information about the connected Pebble smartwatch.
*
* @property {String} platform - The hardware platform, such as `basalt` or `emery`.
* @property {String} model - The watch model, such as `pebble_black`
* @property {String} language - The user's currently selected language on
* this watch.
* @property {Object} firmware - An object containing information about the
* watch's firmware version, including:
* * `major` - The major version
* * `minor` - The minor version
* * `patch` - The patch version
* * `suffix` - Any additional version information, such as `beta3`
*/
/**
* @typedef {Object} AppGlanceSlice
* @memberof Pebble
*
* @desc The structure of an app glance.
*
* @property {String} expirationTime - Optional ISO date-time string of when
the entry should expire and no longer be shown in the app glance.
* @property {Object} layout - An object containing:
* * `icon` - URI string of the icon to display in the app glance, e.g. system://images/ALARM_CLOCK.
* * `subtitleTemplateString` - Template string that will be displayed in the subtitle of the app glance.
*/

View file

@ -0,0 +1,377 @@
/**
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @namespace CanvasRenderingContext2D
* @desc The CanvasRenderingContext2D interface is used for drawing
* rectangles, text, images and other objects onto the canvas element. It
* provides the 2D rendering context for the drawing on the device's display.
*
* The canvas uses a standard x and y
* {@link https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes coordinate system}.
*
* The `CanvasRenderingContext2D` object is obtained as a parameter in the
* {@link /docs/rockyjs/rocky#on rocky.on('draw', ...)} event.
*
* `rocky.on('draw', function(drawEvent) {<br>&nbsp;&nbsp;var ctx = drawEvent.context;<br>});`
*
* The state of pixels is maintained between each draw, so developers are
* responsible for clearing an area, before drawing again.
*
* Please note that this API is still in development and there may be some
* limitations, which are documented below. We will also be adding support
* for more common APIs in future releases.
*/
var CanvasRenderingContext2D = {
/**
* @typedef {Object} TextMetrics
* @desc The TextMetrics interface represents the dimensions of a text
* in the canvas (display), as created by {@link #measureText measureText}.
*
* @property {Number} width - Calculated width of text in pixels.
* @property {Number} height - Calculated height of text in pixels.
*/
/**
* @typedef {Object} Canvas
* @desc Provides information about the device's canvas (display). This is not
* actually a DOM element, it is provided for standards compliance only.
*
* `rocky.on('draw', function(drawEvent) {<br>&nbsp;&nbsp;var ctx = drawEvent.context;<br>&nbsp;&nbsp;var h = ctx.canvas.unobstructedHeight;<br>});`
*
* @property {Number} clientWidth - The full width of the canvas.
* @property {Number} clientHeight - The full height of the canvas.
* @property {Number} unobstructedWidth - The width of the canvas that is not
* obstructed by system overlays (Timeline Quick View).
* @property {Number} unobstructedHeight - The height of the canvas that is
* not obstructed by system overlays (Timeline Quick View).
*/
/**
* @desc Specifies the color to use inside shapes. The default is
* `#000` (black).
*
* #### Options
*
* Possible values:
*
* * Most (but not all) CSS color names. e.g. `blanchedalmond`
* * Pebble color names. e.g. `shockingpink`
* * Hex color codes, short and long. e.g. `#FFFFFF` or `#FFF`
*
* Please note that we currently only support solid colors. You may specifiy
* `transparent` or `clear` for transparency, but we do do not support
* partial transparency or the `#RRGGBBAA` notation yet.
*
* `ctx.fillStyle = 'white';`
*
*/
fillStyle,
/**
* @desc Specifies the color to use for lines around shapes. The
* default is `#000` (black).
*
* #### Options
*
* Possible values:
*
* * Most (but not all) CSS color names. e.g. `blanchedalmond`
* * Pebble color names. e.g. `shockingpink`
* * Hex color codes, short and long. e.g. `#FFFFFF` or `#FFF`
*
* Please note that we currently only support solid colors. You may specifiy
* `transparent` or `clear` for transparency, but we do do not support
* partial transparency or the `#RRGGBBAA` notation yet.
*
* `ctx.strokeStyle = 'red';`
*
*/
strokeStyle,
/**
* @desc A {@link #Canvas Canvas} object containing information about
* the system's canvas (display).
*/
canvas,
/**
* @desc The width of lines drawn (to the nearest integer) with the
* context (`1.0` by default).
*
* `ctx.lineWidth = 8;`
*
*/
lineWidth,
/**
* @desc Specifies the current text style being used when drawing text.
* Although this string uses the same syntax as a CSS font specifier, you
* cannot specifiy arbitrary values and you must only use one of the values below.
*
* The default font is `14px bold Gothic`.
*
* `ctx.font = '28px bold Droid-serif';`
*
* #### Options
*
* Possible values:
*
* * `18px bold Gothic`
* * `14px Gothic`
* * `14px bold Gothic`
* * `18px Gothic`
* * `24px Gothic`
* * `24px bold Gothic`
* * `28px Gothic`
* * `28px bold Gothic`
* * `30px bolder Bitham`
* * `42px bold Bitham`
* * `42px light Bitham`
* * `42px Bitham-numeric`
* * `34px Bitham-numeric`
* * `21px Roboto`
* * `49px Roboto-subset`
* * `28px bold Droid-serif`
* * `20px bold Leco-numbers`
* * `26px bold Leco-numbers-am-pm`
* * `32px bold numbers Leco-numbers`
* * `36px bold numbers Leco-numbers`
* * `38px bold numbers Leco-numbers`
* * `42px bold numbers Leco-numbers`
* * `28px light numbers Leco-numbers`
*/
font,
/**
* @desc Specifies the current text alignment being used when drawing
* text. Beware that the alignment is based on the x-axis coordinate value of
* the {@link #fillText CanvasRenderingContext2D.fillText} method.
*
* `ctx.textAlign = 'center';`
*
* #### Options
*
* Possible values:
*
* * `left` - The text is left-aligned
* * `right` - The text is right-aligned
* * `center` - The text is center-aligned
* * `start` (default) - The text is aligned left, unless using a
* right-to-left language. Currently only left-to-right is supported.
* * `end` - The text is aligned right, unless using a right-to-left
* language. Currently only left-to-right is supported.
*/
textAlign
};
/**
* @desc Sets all pixels in the rectangle at (`x`,`y`) with size
* (`width`, `height`) to black, erasing any previously drawn content.
*
* `ctx.clearRect(0, 0, 144, 168);`
*
* @param {Number} x - The x-axis coordinate of the rectangle's starting point
* @param {Number} y - The y-axis coordinate of the rectangle's starting point
* @param {Number} width - The rectangle's width
* @param {Number} height - The rectangle's height
*/
CanvasRenderingContext2D.clearRect = function(x, y, width, height) { };
/**
* @desc Draws a filled rectangle at (`x`,`y`) with size (`width`, `height`),
* using the current fill style.
*
* `ctx.fillRect(0, 30, 144, 30);`
*
* @param {Number} x - The x-axis coordinate of the rectangle's starting point
* @param {Number} y - The y-axis coordinate of the rectangle's starting point
* @param {Number} width - The rectangle's width
* @param {Number} height - The rectangle's height
*/
CanvasRenderingContext2D.fillRect = function(x, y, width, height) { };
/**
* @desc Paints a rectangle at (`x`,`y`) with size (`width`, `height`),
* using the current stroke style.
*
* `ctx.strokeRect(0, 30, 144, 30);`
*
* @param {Number} x - The x-axis coordinate of the rectangle's starting point
* @param {Number} y - The y-axis coordinate of the rectangle's starting point
* @param {Number} width - The rectangle's width
* @param {Number} height - The rectangle's height
*/
CanvasRenderingContext2D.strokeRect = function(x, y, width, height) { };
/**
* @desc Draws (fills) `text` at the given (`x`,`y`) position.
*
* `ctx.fillText('Hello World', 0, 30, 144);`
*
* @param {String} text - The text to draw
* @param {Number} x - The x-axis coordinate of the text's starting point
* @param {Number} y - The y-axis coordinate of the text's starting point
* @param {Number} maxWidth - (Optional) Maximum width to draw. If specified,
* and the string is wider than the width, the font is adjusted to use a
* smaller font.
*/
CanvasRenderingContext2D.fillText = function(text, x, y, maxWidth) { };
/**
* @desc Returns a {@link #TextMetrics TextMetrics} object containing
* information about `text`.
*
* `var dimensions = ctx.measureText('Hello World');`
*
* @returns {TextMetrics} - A ``TextMetrics`` object with information about
* the measured text
*
* @param {String} text - The text to measure
*/
CanvasRenderingContext2D.measureText = function(text) { };
/**
* @desc Starts a new path by emptying the list of sub-paths. Call this
* method when you want to create a new path.
*
* `ctx.beginPath();`
*/
CanvasRenderingContext2D.beginPath = function() { };
/**
* @desc Causes the point of the pen to move back to the start of the
* current sub-path. It tries to add a straight line (but does not
* actually draw it) from the current point to the start. If the shape has
* already been closed or has only one point, this function does nothing.
*
* `ctx.closePath();`
*/
CanvasRenderingContext2D.closePath = function() { };
/**
* @desc Moves the starting point of a new sub-path to the (`x`,`y`)
* coordinates.
*
* `ctx.moveTo(10, 20);`
*
* @param {Number} x - The destination point on the x-axis
* @param {Number} y - The destination point on the y-axis
*/
CanvasRenderingContext2D.moveTo = function(x, y) { };
/**
* @desc Connects the last point of the sub-path to the (`x`,`y`)
* coordinates with a straight line.
*
* `ctx.lineTo(10, 20);`
*
* @param {Number} x - The destination point on the x-axis
* @param {Number} y - The destination point on the y-axis
*/
CanvasRenderingContext2D.lineTo = function(x, y) { };
/**
* @desc Adds an arc to the path which is centered at (`x`,`y`)
* position with radius `r` starting at `startAngle` and ending at
* `endAngle` going in the direction determined by the `anticlockwise`
* parameter (defaulting to clockwise).
*
* If `startAngle` > `endAngle` nothing will be drawn, and if the difference
* between `startAngle` and `endAngle` exceeds 2π, a full circle will be drawn.
*
* `// Draw a full circle outline<br>ctx.strokeStyle = 'white';<br>ctx.beginPath();<br>ctx.arc(72, 84, 40, 0, 2 * Math.PI, false);<br>ctx.stroke();`
*
* Please note this function does not work with `.fill`, you must use
* {@link #rockyFillRadial CanvasRenderingContext2D.rockyFillRadial} instead.
*
* @param {Number} x - The x-axis coordinate of the arc's center
* @param {Number} y - The y-axis coordinate of the arc's center
* @param {Number} r - The radius of the arc
* @param {Number} startAngle - The angle at which the arc starts, measured
* clockwise from the positive x axis and expressed in radians.
* @param {Number} endAngle - The angle at which the arc ends, measured
* clockwise from the positive x axis and expressed in radians.
* @param {Bool} [anticlockwise] - (Optional) `Boolean` which, if `true`,
* causes the arc to be drawn counter-clockwise between the two angles
* (`false` by default)
*/
CanvasRenderingContext2D.arc = function(x, y, r, startAngle, endAngle, anticlockwise) { };
/**
* @desc Creates a path for a rectangle at position (`x`,`y`) with a
* size that is determined by `width` and `height`. Those four points are
* connected by straight lines and the sub-path is marked as closed, so
* that you can fill or stroke this rectangle.
*
* `ctx.rect(0, 30, 144, 50);`
*
* @param {Number} x - The x-axis coordinate of the rectangle's starting point
* @param {Number} y - The y-axis coordinate of the rectangle's starting point
* @param {Number} width - The rectangle's width
* @param {Number} height - The rectangle's height
*/
CanvasRenderingContext2D.rect = function(x, y, width, height) { };
/**
* @desc Fills the current path with the current {@link #fillStyle fillStyle}.
*
* `ctx.fill();`
*/
CanvasRenderingContext2D.fill = function() { };
/**
* @desc Strokes the current path with the current {@link #strokeStyle strokeStyle}.
*
* `ctx.stroke();`
*/
CanvasRenderingContext2D.stroke = function() { };
/**
* @desc Saves the entire state of the canvas by pushing the current
* state onto a stack.
*
* `ctx.save();`
*/
CanvasRenderingContext2D.save = function() { };
/**
* @desc Restores the most recently saved canvas state by popping the
* top entry in the drawing state stack. If there is no saved state, this
* method does nothing.
*
* `ctx.restore();`
*/
CanvasRenderingContext2D.restore = function() { };
/**
* @desc Fills a circle clockwise between startAngle and endAngle where
* 0 is the start of the circle beginning at the 3 o'clock position on a
* watchface.
*
* If `startAngle` > `endAngle` nothing will be drawn, and if the difference
* between `startAngle` and `endAngle` exceeds 2π, a full circle will be drawn.
*
* `// Draw a filled circle<br>ctx.fillStyle = 'white';<br>ctx.rockyFillRadial(72, 84, 0, 30, 0, 2 * Math.PI);`
*
* @param {Number} x - The x-axis coordinate of the radial's center point
* @param {Number} y - The y-axis coordinate of the radial's center point
* @param {Number} innerRadius - The inner radius of the circle. Use 0 for a full circle
* @param {Number} outerRadius - The outer radius of the circle
* @param {Number} startAngle - Radial starting angle
* @param {Number} endAngle - Radial finishing angle. If smaller than `startAngle` nothing is drawn
*/
CanvasRenderingContext2D.rockyFillRadial = function(x, y, innerRadius, outerRadius, startAngle, endAngle) { };

View file

@ -0,0 +1,64 @@
/**
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @namespace console
* @desc This provides an interface to the app's debugging console.
*
* If you're using {@link https://cloudpebble.net CloudPebble}, these logs
* will appear when you press 'View Logs' after launching your application.
*
* If you're using the local SDK, you can use the `$ pebble logs` command or:
*
* `$ pebble install --emulator basalt --logs`
*
* You can find out more about logging in our
* {@link /guides/debugging/debugging-with-app-logs/ Debugging with App Logs} guide.
*/
var console = new Object();
/**
* @desc Outputs a message to the app's debugging console.
*
* `console.log(rocky.watchInfo.platform);`
*
* @param {...Object} obj - One or more JavaScript objects to output. The string
* representations of each of these objects are appended together in the order
* listed and output.
*/
console.log = function (obj) { };
/**
* @desc Outputs a warning message to the app's debugging console.
*
* `console.warn('Something seems wrong');`
*
* @param {...Object} obj - One or more JavaScript objects to output. The string
* representations of each of these objects are appended together in the order
* listed and output.
*/
console.warn = function (obj) { };
/**
* @desc Outputs an error message to the app's debugging console.
*
* `console.error(JSON.stringify(obj));`
*
* @param {...Object} obj - One or more JavaScript objects to output. The string
* representations of each of these objects are appended together in the order
* listed and output.
*/
console.error = function (obj) { };

View file

@ -0,0 +1,121 @@
/**
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @namespace Date
*
* @desc Creates a JavaScript Date instance that represents a single moment in
* time. Date objects are based on a time value that is the number of
* milliseconds since 1 January, 1970 UTC.
*
* `var d = new Date();`
*
* We fully implement standard JavaScript `Date` functions, such as: `getDay()`, `getHours()` etc.
*
* For full `Date` documentation see:
* {@link https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Date}
*
* ### Locale Date Methods
*
* The locale date methods ({@link #toLocaleString toLocaleString},
* {@link #toLocaleTimeString toLocaleTimeString} and
* {@link #toLocaleDateString toLocaleDateString}) are currently limited in
* their initial implementation.
*
* Available **options**:
*
* **hour12**
*
* Use 12-hour time (as opposed to 24-hour time). Possible values are `true` and `false`; the default is locale dependent (Pebble setting).
*
* **weekday**
*
* The representation of the weekday. Possible values are "narrow", "short", "long".
*
* **year**
*
* The representation of the year. Possible values are "numeric", "2-digit".
*
* **month**
*
* The representation of the month. Possible values are "numeric", "2-digit", "narrow", "short", "long".
*
* **day**
*
* The representation of the day. Possible values are "numeric", "2-digit".
*
* **hour**
*
* The representation of the hour. Possible values are "numeric", "2-digit".
*
* **minute**
*
* The representation of the minute. Possible values are "numeric", "2-digit".
*
* **second**
*
* The representation of the second. Possible values are "numeric", "2-digit".
*
*
* Please note that locale based date and time functions have the following
* limitations at this time:
*
* * You cannot manually specify a locale, it's automatically based upon the
* current device settings. Locale is optional, or you can specify `undefined`.
*
* `console.log(d.toLocaleDateString());`
*
* * Only a single date/time value can be requested in each method call. Do
* NOT request multiple options. e.g. `{hour: 'numeric', minute: '2-digit'}`
*
* `console.log(d.toLocaleTimeString(undefined, {hour: 'numeric'}));`
*/
var Date = new Object();
/**
* @desc This method returns a string with a language sensitive representation
* of this date object.
*
* `d.toLocaleString();`
*
* @param {String} locale - (Optional) The name of the locale.
* @param {Object} options - (Optional) Only a single option is currently supported.
*/
Date.toLocaleString = function(locale, options) { };
/**
* @desc This method returns a string with a language sensitive representation
* of the date portion of this date object.
*
* `d.toLocaleTimeString(undefined, {hour: 'numeric'});`
*
* @param {String} locale - (Optional) The name of the locale.
* @param {Object} options - (Optional) Only a single option is currently supported.
*/
Date.toLocaleTimeString = function(locale, options) { };
/**
* @desc This method returns a string with a language sensitive representation
* of the time portion of this date object.
*
* `d.toLocaleDateString(undefined, {weekday: 'long'});`
*
* @param {String} locale - (Optional) The name of the locale.
* @param {Object} options - (Optional) Only a single option is currently supported.
*/
Date.toLocaleDateString = function() { };

View file

@ -0,0 +1,58 @@
/**
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Global Functions, Members, and Typedefs
/**
* @desc Calls a function after a specified delay.
*
* `var timeoutId = setTimeout(function(...){}, 10000);`
*
* @returns {Number} timeoutId - The ID of the timeout
* @param {Function} fct - The function to execute
* @param {Number} delay - The delay (in ms)
*/
setTimeout = function(fct, delay) { };
/**
* @desc Clears the delay set by ``setTimeout``.
*
* `clearTimeout(timeoutId);`
*
* @param {Number} timeoutId - The ID of the timeout you wish to clear.
*/
clearTimeout = function(timeoutId) { };
/**
* @desc Repeatedly calls a function, with a fixed time delay between
* each call.
*
* `var intervalId = setInterval(function(...){}, 10000);`
*
* @returns {Number} intervalId - The ID of the interval
* @param {Function} fct - The function to execute
* @param {Number} delay - The delay (in ms)
*/
setInterval = function(fct, delay) { };
/**
* @desc Clears the interval set by ``setInterval``.
*
* `clearInterval(intervalId);`
*
* @param {Number} intervalId - The ID of the interval you wish to clear
*/
clearInterval = function(intervalId) { };

View file

@ -0,0 +1,262 @@
/**
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @namespace rocky
*
* @desc Provides an interface for interacting with application context and
* events. Developers can access the Rocky object with the following line of
* code:
*
* `var rocky = require('rocky');`
*
*/
var rocky = {
/**
* @typedef {Object} WatchInfo
* @desc Provides information about the currently connected Pebble smartwatch.
*
* @property {String} model - The name of the Pebble model. (e.g. pebble_time_round_silver_20mm)
* @property {String} platform - The name of the Pebble platform. (e.g. basalt)
* @property {String} language - Not available yet.
* @property {String} firmware - An object with the following fields:
* * `major` - The major version of the smartwatch's firmware.
* * `minor` - The minor version of the smartwatch's firmware.
* * `patch` - The patch version of the smartwatch's firmware.
* * `suffix` - The suffix of the smartwatch's firmware. (e.g. beta3)
*/
/**
* @typedef {Object} UserPreferences
* @desc Provides access to user related settings from the currently connected Pebble smartwatch.
* The size itself will vary between platforms, see the
* {@link /guides/user-interfaces/content-size/ ContentSize guide} for more information.
*
* @property {String} contentSize - Pebble > System > Notifications > Text Size:
* * `small` - Not available on Emery.
* * `medium` - The default setting.
* * `large` - The default setting on Emery.
* * `x-large` - Only available on Emery.
*/
/**
* @typedef {Function} RockyDrawCallback
* @desc The callback function signature to be used with the draw {@link #on event}.
*
* @param {Object} event - An object containing information about the event:
* * `context` - A {@link /docs/rockyjs/CanvasRenderingContext2D CanvasRenderingContext2D}
* object that can be used to draw information on the disply.
*/
/**
* @typedef {Function} RockyTickCallback
* @desc The callback function signature to be used with the `secondchange`,
* `minutechange`, `hourchange` and `daychange` {@link #on events}.
*
* In addition to firing these tick events at the appropriate time change,
* they are also emitted when the application starts.
*
* @param {Object} event - An object containing information about the event:
* * `date` - A JavaScript
* {@link http://www.w3schools.com/jsref/jsref_obj_date.asp date} object
* representing the current time.
*/
/**
* @typedef {Function} RockyMessageCallback
* @desc The callback function signature to be used with the `message`
* {@link #on event}.
*
* @param {Object} event - An object containing information about the event:
* * `type` - The type of event which was triggered.
* * `data` - The data sent within the message.
*/
/**
* @typedef {Function} RockyPostMessageErrorCallback
* @desc The callback function signature to be used with the `postmessageerror`
* {@link #on event}.
*
* @param {Object} event - An object containing information about the event:
* * `type` - The type of event which was triggered.
* * `data` - The data failed to send within the message.
*/
/**
* @typedef {Function} RockyPostMessageConnectedCallback
* @desc The callback function signature to be used with the `postmessageconnected`
* {@link #on event}.
*
* @param {Object} event - An object containing information about the event:
* * `type` - The type of event which was triggered.
*/
/**
* @typedef {Function} RockyPostMessageDisconnectedCallback
* @desc The callback function signature to be used with the `postmessagedisconnected`
* {@link #on event}.
*
* @param {Object} event - An object containing information about the event:
* * `type` - The type of event which was triggered.
*/
/**
* @typedef {Function} RockyMemoryPressureCallback
* @desc The callback function signature to be used with the `memorypressure`
* {@link #on event}.
*
* @param {Object} event - An object containing information about the event:
* * `level` (String) - The current level of memory pressure.
*
* * `high` - This is a critical level, indicating that the application will
* be terminated if memory isn't immediately free'd.
*
* Important Notes:
* - Avoid creating any new objects/arrays/strings when this level is raised.
* - Drop object properties you don't need using the `delete` operator or by assigning `undefined` to it.
* - Don't use the `in` operator in the handler for large objects/arrays. Avoid `for (var x in y)` due to memory constraints.
* - Array has large memory requirements for certain operations/methods. Avoid `Array.pop()`, `Array.slice` and length assignment `Array.length = 123`, on large arrays.
*
* * `normal` - Not yet implemented.
*
* * `low` - Not yet implemented.
*/
/**
* @desc A {@link #WatchInfo WatchInfo} object containing information about the
* connected Pebble smartwatch.
*
* `console.log(rocky.watchInfo.model);<br>&gt; pebble_2_hr_lime`
*
*/
watchInfo,
/**
* @desc A {@link #UserPreferences UserPreferences} object access to user related settings from the currently connected Pebble smartwatch.
*
* `console.log(rocky.userPreferences.contentSize);<br>&gt; medium`
*
*/
userPreferences
};
/**
* @desc Attaches an event handler to the specified events. You may subscribe
* with multiple handlers, but at present there is no way to unsubscribe.
*
* `rocky.on('minutechange', function() {...});`
*
* #### Event Type Options
*
* Possible values:
*
* * `draw` - Provide a {@link #RockyDrawCallback RockyDrawCallback} as the
* callback. The draw event is being emitted after each call to
* {@link #requestDraw requestDraw} but at most once for each screen update,
* even if {@link #requestDraw requestDraw} is called frequently the 'draw'
* event might also fire at other meaningful times (e.g. upon launch).
* * `secondchange` - Provide a {@link #RockyTickCallback RockyTickCallback} as the
* callback. The secondchange event is emitted every time the clock's second changes.
* * `minutechange` - Provide a {@link #RockyTickCallback RockyTickCallback} as the
* callback. The minutechange event is emitted every time the clock's minute changes.
* * `hourchange` - Provide a {@link #RockyTickCallback RockyTickCallback} as the
* callback. The hourchange event is emitted every time the clock's hour changes.
* * `daychange` - Provide a {@link #RockyTickCallback RockyTickCallback} as the
* callback. The daychange event is emitted every time the clock's day changes.
* * `memorypressure` - Provides a {@link #RockyMemoryPressureCallback RockyMemoryPressureCallback}. The
* event is emitted every time there is a notable change in available system memory.
* You can see an example implementation of memory pressure handling {@link https://github.com/pebble-examples/rocky-memorypressure here}.
* * `message` - Provide a {@link #RockyMessageCallback RockyMessageCallback}
* as the callback. The message event is emitted every time the application
* receives a {@link #postMessage postMessage} from the mobile companion.
* * `postmessageconnected` - Provide a {@link #RockyPostMessageConnectedCallback RockyPostMessageConnectedCallback}
* as the callback. The event may be emitted immediately upon subscription,
* if the subsystem is already connected. It is also emitted when connectivity is established.
* * `postmessagedisconnected` - Provide a {@link #RockyPostMessageDisconnectedCallback RockyPostMessageDisconnectedCallback}
* as the callback. The event may be emitted immediately upon subscription,
* if the subsystem is already disconnected. It is also emitted when connectivity is lost.
* * `postmessageerror` - Provide a {@link #RockyPostMessageErrorCallback RockyPostMessageErrorCallback}
* as the callback. The event is emitted when a transmission error occurrs. The type
* of error is not provided, but the message has not been delivered.
*
* @param {String} type - The event being subscribed to.
* @param {Function} callback - A callback function that will be executed when
* the event occurs. See below for more details.
*
*/
rocky.on = function(type, callback) { };
/**
* @desc Attaches an event handler to the specified events. Synonymous with
* [rocky.on()](#on).
*
* `rocky.addEventListener(type, callback);`
*
* @param {String} type - The event being subscribed to.
* @param {Function} callback - A callback function that will be executed when
* the event occurs. See below for more details.
*/
rocky.addEventListener = function(type, callback) { };
/**
* @desc Remove an existing event listener previously registered with
* [rocky.on()](#on) or [rocky.addEventListener()](#addEventListener).
*
* @param {String} type - The type of the event listener to be removed. See
* [rocky.on()](#on) for a list of available event types.
* @param {Function} callback - The existing developer-defined function that was
* previously registered.
*/
rocky.removeEventListener = function(type, callback) { };
/**
* @desc Remove an existing event handler from the specified events. Synonymous
* with [rocky.removeEventListener()](#removeEventListener).
*
* `rocky.off(type, callback);`
*
* @param {String} type - The type of the event listener to be removed. See
* [rocky.on()](#on) for a list of available event types.
* @param {Function} callback - The existing developer-defined function that was
* previously registered.
*/
rocky.off = function(type, callback) { };
/**
* @desc Sends a message to the mobile companion component. Please be aware
* that messages should be kept concise. Each message is queued, so
* `postMessage()` can be called multiple times immediately. If there is a momentary loss of connectivity, queued
* messages may still be delivered, or automatically removed from the queue
* after a few seconds of failed connectivity. Any transmission failures, or
* out of memory errors will be raised via the `postmessageerror` event.
*
* `rocky.postMessage({cmd: 'fetch'});`
*
* @param {Object} data - An object containing the data to deliver to the mobile
* device. This will be received in the `data` field of the `event`
* delivered to the `on('message', ...)` handler.
*/
rocky.postMessage = function(data) { };
/**
* @desc Flags the canvas (display) as requiring a redraw. Invoking this method
* will cause the {@link #on draw event} to be emitted. Only 1 draw event
* will occur, regardless of how many times the redraw is requested before
* the next draw event.
*
* `rocky.on('secondchange', function(e) {<br>&nbsp;&nbsp;rocky.requestDraw();<br>});`
*/
rocky.requestDraw = function() { };

View file

@ -0,0 +1,153 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
module Pebble
# DocClass is a special type of DocElement for structs and unions.
# It acts like a DocGroup in that it parses an XML file, but it acts like a
# DocMember in that it belongs to a group etc.
class DocClass < DocElement
attr_reader :summary, :description, :kind, :position, :id, :name
def initialize(root, dir, platform, kind, id, group)
super(root, platform)
@dir = dir
@group = group
@kind = kind
@children = []
@xml = {}
@code = id
@doxygen_processor = DoxygenProcessor.new(platform)
load_xml(platform)
end
def load_xml(platform)
@xml[platform] = Nokogiri::XML(File.read("#{@dir}/#{platform}/xml/#{@kind}_#{@code}.xml"))
@data[platform] = {}
@name = @xml[platform].at_css('compounddef > compoundname').content.to_s
@id = @xml[platform].at_css('compounddef')['id']
@path = "#{@group.path}##{@name}"
create_members(platform)
end
def to_liquid
{
'name' => @name,
'summary' => @summary,
'description' => @description,
'url' => url,
'children' => @children,
'data' => @data,
'platforms' => @xml.keys,
'uniform' => uniform?
}
end
def process(mapping)
@xml.each do |platform, xml|
@data[platform]['summary'] = @doxygen_processor.process_summary(
xml.at_css('compounddef > briefdescription'), mapping
)
description = xml.at_css('compounddef > detaileddescription')
process_simplesects(description, mapping, platform)
@data[platform]['description'] = @doxygen_processor.process_description(
description, mapping)
process_members(mapping, platform)
end
@children = @children.reject { |child| child.name.match(/^@/) }
@children.sort! { |m, n| m.position <=> n.position }
end
def uniform?
identical = @data['aplite'].to_json == @data['basalt'].to_json
identical &&= @children.all?(&:uniform?)
identical
end
private
def create_members(platform)
@xml[platform].css('memberdef').each do |child|
variable = DocClassVariable.new(@root, child, @group, platform)
existing = @children.select { |ex| ex.name == variable.name }.first
if existing.nil?
@children << variable
else
existing.add_platform(platform, child)
end
end
end
def process_members(mapping, platform)
@children.each { |child| child.process(mapping, platform) }
end
end
# DocClassVariable is a DocElement subclass that handles the members (or
# variables) of a struct or union.
class DocClassVariable < DocElement
attr_reader :name, :position
def initialize(root, node, group, platform)
super(root, platform)
@name = node.at_css('name').content.to_s
@group = group
@nodes = { platform => node }
@platforms = [platform]
@path = "#{@group.path}##{@name}"
@position = node.at_css(' > location')['line'].to_i
end
def add_platform(platform, node)
@nodes[platform] = node
@platforms << platform
@data[platform] = {}
end
def to_liquid
{
'name' => @name,
'data' => @data,
'url' => url,
'type' => @type,
'platforms' => @platforms
}
end
def uniform?
@data['aplite'].to_json == @data['basalt'].to_json
end
def process(mapping, platform)
return unless @platforms.include? platform
@data[platform]['summary'] = @doxygen_processor.process_summary(
@nodes[platform].at_css('briefdescription'), mapping
)
description = @nodes[platform].at_css('detaileddescription')
process_simplesects(description, mapping, platform)
process_type(mapping, platform)
@data[platform]['description'] = @doxygen_processor.process_description(
description, mapping)
end
def process_type(mapping, platform)
if @nodes[platform].at_css('type > ref').nil?
@data[platform]['type'] = @nodes[platform].at_css('type').content.to_s
else
type_node = @nodes[platform].at_css('type').clone()
@doxygen_processor.process_node_ref(type_node.at_css('ref'), mapping)
@data[platform]['type'] = type_node.to_html.to_s
end
end
end
end

View file

@ -0,0 +1,98 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
module Pebble
# DocElement is an abstract C Documentation class that is subclasses for
# each of the various items that build up a documentation page, symbol or
# tree item.
class DocElement
KNOWN_SECT_TYPES = %w(return note)
attr_reader :url
def initialize(root, platform)
@root = root
@data = {}
@data[platform] = {}
@doxygen_processor = DoxygenProcessor.new(platform)
end
def to_symbol
{
name: @name,
url: url,
summary: default_data('summary')
}
end
def to_mapping
{
id: @id,
url: url
}
end
private
def default_data(key)
return @data['basalt'][key] unless @data['basalt'].nil? || @data['basalt'][key].nil?
return @data['aplite'][key] unless @data['aplite'].nil? || @data['aplite'][key].nil?
''
end
def url
"#{@root}#{@path}"
end
def add_data(type, value, platform)
@data[platform] = {} if @data[platform].nil?
@data[platform][type] = [] if @data[platform][type].nil?
@data[platform][type] << value
end
def process_simplesects(node, mapping, platform)
if node.name.to_s == 'detaileddescription'
desc = node
else
desc = node.at_css('detaileddescription')
end
return if desc.nil?
desc.css('simplesect').each do |sect|
if KNOWN_SECT_TYPES.include?(sect['kind'])
process_simplesect_basic(sect, mapping, platform)
elsif sect['kind'] == 'see'
process_simplesect_see(sect, mapping, platform)
end
end
end
def process_simplesect_basic(sect, mapping, platform)
value = @doxygen_processor.process_summary(sect.clone, mapping)
add_data(sect['kind'], value, platform)
sect.remove
end
def process_simplesect_see(sect, mapping, platform)
if sect.at_css('para > ref').nil?
add_data(sect['kind'],
@doxygen_processor.process_paragraph(sect.at_css('para'),
mapping), platform)
else
see_node = sect.at_css('para > ref').clone
@doxygen_processor.process_node_ref(see_node, mapping)
add_data(sect['kind'], see_node.to_html.to_s, platform)
end
sect.remove
end
end
end

View file

@ -0,0 +1,58 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
module Pebble
# DocEnumValue is a DocElement that is one of the possible values of an enum.
class DocEnumValue < DocElement
attr_reader :position, :summary, :id, :platforms, :name, :data
def initialize(root, node, group, platform)
super(root, platform)
@name = node.at_css('name').content.to_s
@id = node['id']
@group = group
@path = "#{@group.path}##{@name}"
@nodes = { platform => node }
@platforms = [platform]
@doxygen_processor = DoxygenProcessor.new(platform)
end
def add_platform(node, platform)
@nodes[platform] = node
@platforms << platform
@data[platform] = {}
end
def process(mapping, platform)
return unless @platforms.include? platform
process_simplesects(@nodes[platform], mapping, platform)
@data[platform]['summary'] = @doxygen_processor.process_summary(
@nodes[platform].at_css('briefdescription'), mapping
)
end
def uniform?
data['aplite'].to_json == data['basalt'].to_json
end
def to_liquid
{
'name' => @name,
'data' => @data,
'url' => url,
'platforms' => @platforms
}
end
end
end

View file

@ -0,0 +1,178 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require_relative 'doc_element.rb'
require_relative 'doc_member.rb'
require_relative 'doc_class.rb'
require_relative 'doxygen_processor.rb'
module Pebble
# A DocGroup is a a collection of members, structs and subgroups that will
# become a page in the C documentation.
class DocGroup < DocElement
attr_accessor :groups, :members, :name, :path, :menu_path, :classes, :id
def initialize(root, dir, platform, id, menu_path = [])
super(root, platform)
@dir = dir
@menu_path = Array.new(menu_path)
@xml = {}
@groups = []
@members = []
@classes = []
@group_id = id
@doxygen_processor = DoxygenProcessor.new(platform)
@root = root
load_xml(platform)
end
def load_xml(platform)
@xml[platform] = Nokogiri::XML(File.read("#{@dir}/#{platform}/xml/group___#{@group_id}.xml"))
@id = @xml[platform].at_css('compounddef')['id']
@name = @xml[platform].at_css('compounddef > title').content.to_s
@menu_path << @name if @path.nil?
@path = @menu_path.join('/').gsub(' ', '_') + '/'
create_descendents(platform)
end
def process(mapping, platform)
return if @xml[platform].nil?
@data[platform] = {} if @data[platform].nil?
@data[platform]['summary'] = @doxygen_processor.process_summary(
@xml[platform].at_css('compounddef > briefdescription'), mapping)
description = @xml[platform].at_css('compounddef > detaileddescription')
process_simplesects(description, mapping, platform)
@data[platform]['description'] = @doxygen_processor.process_description(
description, mapping)
process_descendents(mapping, platform)
end
def to_page(site)
PageDocC.new(site, @root, site.source, "#{@path}index.html", self)
end
def to_branch
{
'name' => @name,
'url' => url,
'children' => @groups.map(&:to_branch),
'summary' => default_data('summary')
}
end
def mapping_array
mapping = [to_mapping]
@groups.each { |group| mapping += group.mapping_array }
@members.each { |member| mapping << member.to_mapping }
@classes.each { |cls| mapping << cls.to_mapping }
mapping
end
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
def to_liquid
{
'name' => @name,
'url' => url,
'path' => @menu_path,
'groups' => @groups,
'members' => @members,
'functions' => @members.select { |member| member.kind == 'function' },
'enums' => @members.select { |member| member.kind == 'enum' },
'defines' => @members.select { |member| member.kind == 'define' },
'typedefs' => @members.select { |member| member.kind == 'typedef' },
'structs' => @classes.select { |member| member.kind == 'struct' },
'unions' => @classes.select { |member| member.kind == 'union' },
'data' => @data,
'basalt_only' => !@xml.key?('aplite'),
'summary' => default_data('summary'),
'description' => default_data('description')
}
end
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
private
def create_descendents(platform)
create_inner_groups(platform)
create_members(platform)
create_inner_classes(platform)
@members.sort! { |m, n| m.position <=> n.position }
end
def create_inner_groups(platform)
@xml[platform].css('innergroup').each do |child|
id = child['refid'].sub(/^group___/, '')
new_group = DocGroup.new(@root, @dir, platform, id, @menu_path)
group = @groups.select { |grp| new_group.name == grp.name }.first
if group.nil?
@groups << new_group
else
group.load_xml(platform)
end
end
end
def create_members(platform)
@xml[platform].css('memberdef').map do |child|
new_member = DocMember.new(@root, child, self, platform)
member = @members.select { |mem| mem.name == new_member.name }.first
if member.nil?
@members << new_member
else
member.add_platform(platform, child)
end
end
end
def create_inner_classes(platform)
@xml[platform].css('innerclass').map do |child|
next if child.content.to_s.match(/__unnamed__/)
next if child.content.to_s.match(/\./)
if child['refid'].match(/^struct_/)
create_struct(child, platform)
elsif child['refid'].match(/^union_/)
create_union(child, platform)
end
end
end
def create_struct(node, platform)
id = node['refid'].sub(/^struct_/, '')
new_struct = DocClass.new(@root, @dir, platform, 'struct', id, self)
struct = @classes.select { |str| new_struct.name == str.name }.first
if struct.nil?
@classes << new_struct
else
struct.load_xml(platform)
end
end
def create_union(node, platform)
id = node['refid'].sub(/^union_/, '')
new_union = DocClass.new(@root, @dir, platform, 'union', id, self)
union = @classes.select { |un| un.name == new_union.name }.first
if union.nil?
@classes << new_union
else
union.load_xml(platform)
end
end
def process_descendents(mapping, platform)
@groups.each { |group| group.process(mapping, platform) }
@members.each { |member| member.process(mapping, platform) }
@classes.each { |member| member.process(mapping) }
end
end
end

View file

@ -0,0 +1,165 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require_relative 'doc_enum_value.rb'
module Pebble
# A DocMember is a function, enum, typedef that will become a symbol
# and be a part of a documentation page. Belongs to a DocGroup.
class DocMember < DocElement
attr_accessor :children, :kind, :name, :summary, :children, :position, :data, :id
def initialize(root, node, group, platform)
super(root, platform)
@group = group
@children = []
@platforms = [platform]
@nodes = { platform => node }
@name = node.at_css('name').content.to_s
@kind = node['kind']
@id = node['id']
@path = "#{@group.path}##{@name}"
@position = node.at_css(' > location')['line'].to_i
@doxygen_processor = DoxygenProcessor.new(platform)
create_enum_values(node, platform) if @kind == 'enum'
end
def add_platform(platform, node)
@platforms << platform
@nodes[platform] = node
@data[platform] = {}
create_enum_values(node, platform) if @kind == 'enum'
end
def to_liquid
{
'name' => @name,
'url' => url,
'children' => @children,
'position' => @position,
'data' => @data,
'uniform' => uniform?,
'platforms' => @platforms
}
end
def process(mapping, platform)
return unless @platforms.include? platform
@data[platform]['summary'] = @doxygen_processor.process_summary(
@nodes[platform].at_css(' > briefdescription'), mapping
)
process_data(@nodes[platform], mapping, platform)
@data[platform]['description'] = @doxygen_processor.process_description(
@nodes[platform].at_css(' > detaileddescription'), mapping
)
@children.each { |child| child.process(mapping, platform) }
end
def uniform?
identical = data['aplite'].to_json == data['basalt'].to_json
identical &&= children.all?(&:uniform?) if @kind == 'enum'
identical
end
private
def create_enum_values(node, platform)
node.css('enumvalue').each do |value|
enum_value = DocEnumValue.new(@root, value, @group, platform)
existing_value = @children.select { |ev| ev.name == enum_value.name }.first
if existing_value.nil?
@children << enum_value
else
existing_value.add_platform(value, platform)
end
end
end
def process_data(node, mapping, platform)
process_typedef(node, mapping, platform) if @kind == 'typedef'
process_function(node, mapping, platform) if @kind == 'function'
process_define(node, mapping, platform) if @kind == 'define'
process_simplesects(node, mapping, platform)
end
def process_typedef(node, mapping, platform)
process_return_type(node, mapping, platform)
@data[platform]['argsstring'] = node.at_css('argsstring').content.to_s
process_parameter_list(node, mapping, platform)
end
def process_function(node, mapping, platform)
process_return_type(node, mapping, platform)
process_params(node, mapping, platform) unless node.css('param').nil?
process_parameter_list(node, mapping, platform)
end
def process_define(node, mapping, platform)
unless node.at_css('initializer').nil?
@data[platform]['initializer'] = process_to_html(
node.at_css('initializer'), mapping
)
end
process_return_type(node, mapping, platform)
process_parameter_list(node, mapping, platform)
process_params(node, mapping, platform) unless node.css('param').nil?
end
def process_return_type(node, mapping, platform)
@data[platform]['type'] = process_to_html(node.at_css('> type'), mapping)
end
def process_params(node, mapping, platform)
@data[platform]['params'] = node.css('param').map do |elem|
params = {}
unless elem.at_css('declname').nil?
params['name'] = elem.at_css('declname').content.to_s
end
unless elem.at_css('type').nil?
params['type'] = process_to_html(elem.at_css('type'), mapping)
end
unless elem.at_css('defname').nil?
params['name'] = elem.at_css('defname').content.to_s
end
params
end
end
def process_to_html(node, mapping)
return '' if node.nil?
node.css('ref').each do |ref|
@doxygen_processor.process_node_ref(ref, mapping)
end
node.inner_html.to_s
end
def process_parameter_list(node, mapping, platform)
return if node.at_css('parameterlist').nil?
parameter_list = node.at_css('parameterlist').clone
node.at_css('parameterlist').remove
@data[platform]['parameters'] = parameter_list.css('parameteritem').map do |item|
{
'name' => get_parameter_name(item),
'summary' => @doxygen_processor.process_summary(item.at_css('parameterdescription'), mapping)
}
end
end
def get_parameter_name(item)
name = item.at_css('parameternamelist > parametername')
direction = name.attr('direction').to_s
direction.nil? || direction == '' ? name.content.to_s : "#{name.content.to_s} (#{direction})"
end
end
end

View file

@ -0,0 +1,154 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
module Pebble
# Class of methods for processing Doxygen XML into 'sane' HTML.
# rubocop:disable Metrics/ClassLength
class DoxygenProcessor
def initialize(platform)
@platform = platform
end
def process_summary(node, mapping)
process_description(node, mapping)
end
def process_description(node, mapping)
return '' if node.nil?
node.children.map { |para| process_paragraph(para, mapping) }.join("\n").strip
end
# rubocop:disable Metrics/MethodLength, Methods/CyclomaticComplexity
# rubocop:disable Methods/AbcSize
def process_paragraph(node, mapping)
return '' if node.nil?
node.name = 'p'
node.children.each do |child|
case child.name
when 'ref'
process_node_ref(child, mapping)
when 'programlisting'
process_code(child)
when 'simplesect'
# puts node.content.to_s
# process_blockquote(child)
when 'heading'
process_node_heading(child)
when 'htmlonly'
process_node_htmlonly(child)
when 'itemizedlist'
process_list(child, mapping)
when 'image'
process_image(child)
when 'computeroutput'
process_computer_output(child)
when 'emphasis'
child.name = 'em'
when 'bold'
child.name = 'strong'
when 'linebreak'
child.name = 'br'
when 'preformatted'
child.name = 'pre'
when 'ndash'
child.name = 'span'
child.content = '-'
when 'ulink'
child.name = 'a'
child['href'] = child['url'].sub(%r{^https?://developer.pebble.com/}, '/')
child.remove_attribute('url')
when 'text'
# SKIP!
else
# puts child.name, node.content.to_s
end
end
node.to_html.to_s.strip
end
# rubocop:enable Metrics/MethodLength, Methods/CyclomaticComplexity
# rubocop:enable Methods/AbcSize
def process_code(node)
xml = node.to_xml.gsub('<sp/>', ' ')
doc = Nokogiri::XML(xml)
highlight = Pygments.highlight(doc.content.to_s.strip, lexer: 'c')
node.content = ''
node << Nokogiri::XML(highlight).at_css('pre')
node.name = 'div'
node['class'] = 'highlight'
end
def process_node_ref(child, mapping)
child.name = 'a'
map = mapping.select { |m| m[:id] == child['refid'] }.first
child['href'] = map[:url] unless map.nil?
end
def process_node_heading(node)
node.name = 'h' + node['level']
end
def process_node_htmlonly(node)
decoder = HTMLEntities.new
xml = Nokogiri::XML('<root>' + decoder.decode(node.content) + '</root>')
node_count = xml.root.children.size
process_node_htmlonly_simple(node, xml) if node_count == 2
process_node_htmlonly_complex(node, xml) if node_count > 2
end
def process_node_htmlonly_simple(node, xml)
child = xml.at_css('root').children[0]
node.name = child.name
child.attributes.each { |key, value| node[key] = value }
node.content = child.content
end
def process_node_htmlonly_complex(node, xml)
node.name = 'div'
node.content = ''
node << xml.root.children
end
def process_blockquote(node)
node.name = 'blockquote'
node['class'] = 'blockquote--' + node['kind']
process_paragraph(node.children[0]) if node.children[0].name == 'para'
node.to_html.to_s
end
def process_list(node, mapping)
node.name = 'ul'
node.children.each do |child|
process_list_item(child, mapping)
end
end
def process_list_item(node, mapping)
node.name = 'li'
node.children.each do |child|
process_paragraph(child, mapping) if child.name == 'para'
end
end
def process_image(node)
node.name = 'img'
node['src'] = "/assets/images/docs/c/#{@platform}/#{node['name']}"
end
def process_computer_output(node)
node.name = 'code'
end
end
# rubocop:enable Metrics/ClassLength
end

View file

@ -0,0 +1,49 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
module Pebble
class Documentation
LANGUAGE = ''
def initialize(site)
@site = site
@symbols = []
@pages = []
@tree = []
end
def load_symbols(symbols)
symbols.concat(@symbols)
end
def create_pages(pages)
pages.concat(@pages)
end
def build_tree(tree)
tree.concat(@tree)
end
private
def add_symbol(symbol)
symbol[:language] = language
symbol[:summary] = symbol[:summary].strip unless symbol[:summary].nil?
@symbols << symbol
end
def language
LANGUAGE
end
end
end

View file

@ -0,0 +1,189 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require 'pygments'
require 'zip'
require 'nokogiri'
require 'htmlentities'
require_relative 'c_docs/doc_group.rb'
module Pebble
# Pebble C documentation processing class.
class DocumentationC < Documentation
MASTER_GROUP_IDS = %w(foundation graphics u_i smartstrap worker standard_c)
def initialize(site, source, root, language='c')
super(site)
@site = site
@url_root = root
@source = source
@tmp_dir = 'tmp/docs/c'
@groups = []
@language = language
run
end
private
def language
@language
end
def run
cleanup
download_and_extract(@source, @tmp_dir)
hack_smartstraps
process
add_images
end
def cleanup
FileUtils.rmtree @tmp_dir
end
def download_and_extract(zip, folder)
open(zip) do | zf |
Zip::File.open(zf.path) do | zipfile |
zipfile.each do | entry |
path = File.join(folder, entry.name).sub('/doxygen_sdk/', '/')
FileUtils.mkdir_p(File.dirname(path))
zipfile.extract(entry, path) unless File.exist?(path)
end
end
end
end
# This is a hack to get around a limitation with the documentation generator.
# At present, it cannot handle the situation where a top level doc group exists on
# Basalt but not Aplite.
# Smartstraps is the only group that fits this pattern at the moment.
# This hack copies the XML doc from the Basalt folder to the Aplite folder and removes
# all of its contents.
def hack_smartstraps
basalt_xml = Nokogiri::XML(File.read("#{@tmp_dir}/basalt/xml/group___smartstrap.xml"))
basalt_xml.search('.//memberdef').remove
basalt_xml.search('.//innerclass').remove
basalt_xml.search('.//sectiondef').remove
File.open("#{@tmp_dir}/aplite/xml/group___smartstrap.xml", 'w') do |file|
file.write(basalt_xml.to_xml)
end
end
def process
DocumentationC::MASTER_GROUP_IDS.each do |id|
@groups << DocGroup.new(@url_root, @tmp_dir, 'aplite', id)
end
@groups.each { |group| group.load_xml('basalt') }
mapping = []
@groups.each { |group| mapping += group.mapping_array }
@groups.each do |group|
group.process(mapping, 'aplite')
group.process(mapping, 'basalt')
end
add_symbols(@groups)
@groups.each { |group| @tree << group.to_branch }
add_pages(@groups)
add_redirects(mapping)
end
def add_images
move_images('aplite')
move_images('basalt')
images = Dir.glob("#{@tmp_dir}/assets/images/**/*.png")
images.each do |img|
source = File.join(@site.source, '../tmp/docs/c/')
if File.exists?(img)
img.sub!('tmp/docs/c', '')
@site.static_files << Jekyll::StaticFile.new(@site, source, '', img)
end
end
end
def move_images(platform)
images = Dir.glob("#{@tmp_dir}/#{platform}/**/*.png")
dir = File.join(@tmp_dir, 'assets', 'images', 'docs', 'c', platform)
FileUtils.mkdir_p(dir)
images.each do |img|
FileUtils.cp(img, File.join(dir, File.basename(img)))
end
end
# TODO: Make the groups handle their own subgroups and members etc
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
def add_symbols(groups)
groups.each do |group|
add_symbol(group.to_symbol)
group.members.each do |member|
add_symbol(member.to_symbol)
member.children.each do |child|
add_symbol(child.to_symbol)
end
end
group.classes.each do |child|
add_symbol(child.to_symbol)
# OPINION: I don't think we want to have struct members as symbols.
# struct.children.each do |child|
# add_symbol(child.to_symbol)
# end
end
add_symbols(group.groups)
end
end
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
def add_pages(groups)
groups.each do |group|
page = group.to_page(@site)
page.set_language(@language)
@pages << page
add_pages(group.groups)
end
end
def add_redirects(mapping)
mapping.each do |map|
next if map[:id].match(/_1/)
@site.pages << Jekyll::RedirectPage.new(@site, @site.source, @url_root, map[:id] + '.html', map[:url])
end
end
end
# Jekyll Page subclass for rendering the C documentation pages.
class PageDocC < Jekyll::Page
attr_reader :group
def initialize(site, root, base, dir, group)
@site = site
@base = base
@dir = root
@name = dir
@group = group
process(@name)
read_yaml(File.join(base, '_layouts', 'docs'), 'c.html')
data['title'] = @group.name
data['platforms'] = %w(aplite basalt)
end
def set_language(language)
data['docs_language'] = language
end
def to_liquid(attrs = ATTRIBUTES_FOR_LIQUID)
super(attrs + %w(group))
end
end
end

View file

@ -0,0 +1,120 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require 'zip'
module Pebble
# Rock.js documentation processing class.
class DocumentationJs < Documentation
def initialize(site, source, root, language, preview = false)
super(site)
@url_root = root
@language = language
@preview = preview
json = site.data[source]
json.each { |js_module| process_module(js_module) }
end
private
def process_module(js_module)
js_module[:path] = js_module['name']
js_module[:url] = module_url(js_module)
js_module[:processed_functions] = []
js_module[:processed_members] = []
js_module[:processed_typedefs] = []
js_module[:children] = []
process_members(js_module)
add_to_tree(js_module)
# Create and add the page
page = PageDocJS.new(@site, module_url(js_module), js_module)
page.set_data(@language, @preview)
@pages << page
end
def process_members(js_module)
js_module['members'].each do |type, members|
members.each do |member|
kind = member.key?('kind') ? member['kind'] : 'member'
processed_type = 'processed_' + kind + 's'
url = child_url(js_module, member)
symbol = {
:name => member['name'],
:description => member['description'],
:type => member['type'],
:returns => member['returns'],
:params => member['params'],
:properties => member['properties'],
:url => url,
:kind => kind,
:summary => member['summary']
}
add_symbol(symbol)
js_module[:children].push(symbol)
js_module[processed_type.to_sym].push(symbol)
end
end
end
def add_to_tree(js_module)
@tree << js_module
end
def module_url(js_module)
"#{@url_root}#{js_module['name']}/"
end
def child_url(js_module, child)
"#{module_url(js_module)}##{child['name']}"
end
def child_path(js_module, child)
[js_module['name'], child['name']].join('.')
end
def language
@language
end
end
# Jekyll Page subclass for rendering the JS documentation pages.
class PageDocJS < Jekyll::Page
attr_reader :js_module
def initialize(site, url, js_module)
@site = site
@base = site.source
@dir = url
@name = 'index.html'
@js_module = JSON.parse(js_module.to_json)
process(@name)
read_yaml(File.join(@base, '_layouts', 'docs'), 'js.html')
data['title'] = js_module['name']
end
def set_data(language, preview)
data['docs_language'] = language
data['preview'] = preview
end
def to_liquid(attrs = ATTRIBUTES_FOR_LIQUID)
super(attrs + %w(js_module))
end
end
end

View file

@ -0,0 +1,203 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require 'zip'
require 'nokogiri'
require_relative 'pebble_documentation'
# TODO: Error handling.
# TODO: Introduce some DRY
# TODO: Fix the internal links!
# TODO: Bring back the summarys which I broke.
# TODO: Android Index Page
module Pebble
class DocumentationPebbleKitAndroid < Documentation
def initialize(site, source)
super(site)
@path = '/docs/pebblekit-android/'
open(source) do | zf |
Zip::File.open(zf.path) do | zipfile |
entry = zipfile.glob('javadoc/overview-summary.html').first
summary = Nokogiri::HTML(entry.get_input_stream.read)
process_summary(zipfile, summary)
@pages << PageDocPebbleKitAndroid.new(@site, @site.source, 'docs/pebblekit-android/com/constant-values/', 'Constant Values', process_html(Nokogiri::HTML(zipfile.glob('javadoc/constant-values.html').first.get_input_stream.read).at_css('.constantValuesContainer').to_html, '/docs/pebblekit-android/'), nil)
@pages << PageDocPebbleKitAndroid.new(@site, @site.source, 'docs/pebblekit-android/com/serialized-form/', 'Serialized Form', process_html(Nokogiri::HTML(zipfile.glob('javadoc/serialized-form.html').first.get_input_stream.read).at_css('.serializedFormContainer').to_html, '/docs/pebblekit-android/'), nil)
end
end
end
private
def language
'pebblekit_android'
end
def process_summary(zipfile, summary)
summary.css('tbody tr').each do | row |
name = row.at_css('td.colFirst').content
package = {
name: name,
url: "#{@path}#{name_to_url(name)}/",
children: [],
methods: [],
enums: [],
exceptions: [],
path: [name]
}
add_symbol(name: name, url: "#{@path}#{name_to_url(name)}/")
@tree << package
end
@tree.each do | package |
entry = zipfile.glob("javadoc/#{name_to_url(package[:name])}/package-summary.html").first
summary = Nokogiri::HTML(entry.get_input_stream.read)
process_package(zipfile, package, summary)
end
end
def process_package(zipfile, package, summary)
url = "#{@path}#{name_to_url(package[:name])}"
html = summary.at_css('.contentContainer').to_html
html = process_html(html, url)
@pages << PageDocPebbleKitAndroid.new(@site, @site.source, url, package[:name], html, package)
class_table = summary.css('table[summary~="Class Summary"]')
class_table.css('tbody tr').each do | row |
name = row.at_css('td.colFirst').content
package[:children] << {
name: name,
summary: row.at_css('.colLast').content,
url: "#{url}/#{name}",
path: package[:path].clone << name,
type: 'class',
children: [],
methods: [],
enums: [],
exceptions: []
}
add_symbol(name: "#{package[:name]}.#{name}", url: "#{url}/#{name}")
end
enum_table = summary.css('table[summary~="Enum Summary"]')
enum_table.css('tbody tr').each do | row |
name = row.at_css('.colFirst').content
package[:children] << {
name: name,
summary: row.at_css('.colLast').content,
path: package[:path].clone << name,
url: "#{url}/#{name}",
type: 'enum',
children: [],
methods: [],
enums: [],
exceptions: []
}
add_symbol(name: "#{package[:name]}.#{name}", url: "#{url}/#{name}")
end
summary.css('table[summary~="Exception Summary"]').css('tbody tr').each do | row |
name = row.at_css('td.colFirst').content
package[:children] << {
name: name,
summary: row.at_css('.colLast').content,
path: package[:path].clone << name,
url: "#{url}/#{name}",
type: 'exception',
children: [],
methods: [],
enums: [],
exceptions: []
}
add_symbol(name: "#{package[:name]}.#{name}", url: "#{url}/#{name}")
end
package[:children].each do | child |
filename = "javadoc/#{name_to_url(package[:name])}/#{child[:name]}.html"
child_url = '/docs/pebblekit-android/' + package[:name].split('.').join('/') + '/' + child[:name] + '/'
entry = zipfile.glob(filename).first
summary = Nokogiri::HTML(entry.get_input_stream.read)
method_table = summary.css('table[summary~="Method Summary"]')
method_table.css('tr').each do | row |
next unless row.at_css('.memberNameLink')
name = row.at_css('.memberNameLink').content
child[:methods] << {
name: name,
summary: row.at_css('.block') ? row.at_css('.block').content : '',
url: child_url + '#' + name,
type: 'method'
}
add_symbol(name: [package[:name], child[:name], name].join('.'), url: child_url + '#' + name)
end
html = summary.at_css('.contentContainer').to_html
html = process_html(html, child_url)
@pages << PageDocPebbleKitAndroid.new(@site, @site.source, child_url, child[:name], html, child)
end
end
def name_to_url(name)
name.split('.').join('/')
end
def process_html(html, root)
contents = Nokogiri::HTML(html)
contents.css('a').each do | link |
next if link['href'].nil?
href = File.expand_path(link['href'], root)
href = href.sub('/com/com/', '/com/')
href = href.sub('.html', '/')
link['href'] = href
end
contents.css('.memberSummary caption').remove
contents.to_html
end
end
class PageDocPebbleKitAndroid < Jekyll::Page
def initialize(site, base, dir, title, contents, group)
@site = site
@base = base
@dir = dir
@name = 'index.html'
@contents = contents
@group = group
process(@name)
read_yaml(File.join(base, '_layouts', 'docs'), 'pebblekit-android.html')
data['title'] = title.to_s
end
def to_liquid(attrs = ATTRIBUTES_FOR_LIQUID)
super(attrs + %w(
contents
group
))
end
attr_reader :contents
def group
if @group.nil?
{}
else
JSON.parse(JSON.dump(@group))
end
end
end
end

View file

@ -0,0 +1,240 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require 'zip'
require 'nokogiri'
require 'slugize'
require 'open-uri'
module Pebble
# PebbleKit iOS documentation processing class.
class DocumentationPebbleKitIos < Documentation
def initialize(site, source, root)
super(site)
@site = site
@url_root = root
open(source) do |zf|
Zip::File.open(zf.path) do |zipfile|
zipfile.each { |entry| process_entry(entry) }
end
end
end
private
def language
'pebblekit_ios'
end
def process_entry(entry)
return unless File.extname(entry.name) == '.html'
doc = Nokogiri::HTML(entry.get_input_stream.read)
process_index(doc) if File.basename(entry.name) == 'index.html'
process_normal_entry(doc, entry)
end
def process_normal_entry(doc, entry)
doc_entry = DocEntryPebbleKitIos.new(entry, doc, @url_root)
add_symbol(doc_entry.to_symbol)
doc_entry.anchor_symbols.map { |symbol| add_symbol(symbol) }
@pages << doc_entry.create_page(@site)
end
def process_index(doc)
headers = doc.at_css('#content').css('h2').map(&:content)
lists = doc.at_css('#content').css('ul').map { | list | list.css('li') }
headers.each_with_index do |header, index|
process_index_header(header, index, lists)
end
end
def process_index_header(header, index, lists)
tree_item = {
name: header,
url: "#{@url_root}##{header.slugize}",
children: []
}
lists[index].each { |item| process_index_header_item(tree_item, item) }
@tree << tree_item
end
def process_index_header_item(tree_item, item)
tree_item[:children] << {
name: item.content,
url: "#{@url_root}#{item.at_css('a')['href'].sub('.html', '/').gsub('+', '%2B')}",
path: [item.content],
children: []
}
end
end
# DocEntryIos is an iOS documentation class used to process a single page
# of the iOS documentation.
class DocEntryPebbleKitIos
def initialize(entry, doc, url_root)
@entry = entry
@doc = doc
@url_root = url_root
end
def to_symbol
{ name: name, url: url.gsub('+', '%2B') }
end
def anchor_symbols
@doc.css('a[name^="//api"][title]').map do |anchor|
anchor_to_symbol(anchor)
end
end
def create_page(site)
return nil if @doc.at_css('#content').nil?
contents = @doc.at_css('#content')
title = @doc.at_css('.title').content
group = { 'path' => [File.basename(path)] }
PageDocPebbleKitIos.new(site, url, title, contents, group)
end
private
def name
File.basename(@entry.name).sub('.html', '')
end
def url
@url_root + path
end
def path
@entry.name.sub('.html', '/')
end
def anchor_to_symbol(anchor)
summary = @doc.at_css("a[name=\"#{anchor['name']}\"] + h3 + div")
{
name: anchor['title'],
url: (url + '#' + anchor['name']).gsub('+', '%2B'),
summary: summary.content
}
end
end
# Jekyll Page subclass for rendering the iOS documentation pages.
class PageDocPebbleKitIos < Jekyll::Page
attr_reader :group
def initialize(site, dir, title, contents, group)
@site = site
@base = site.source
@dir = dir
@name = 'index.html'
@contents = contents
@group = group
process(@name)
process_contents
read_yaml(File.join(@base, '_layouts', 'docs'), 'pebblekit-ios.html')
data['title'] = title
end
def to_liquid(attrs = ATTRIBUTES_FOR_LIQUID)
super(attrs + %w(
contents
group
))
end
def contents
@contents.to_html
end
private
def process_contents
# Clean up links
@contents.css('a').each { |link| process_page_link(link) }
remove_duplicated_title
switch_specification_section_table_headers_to_normal_cells
clean_up_method_titles
switch_parameter_tables_into_definition_lists
remove_footer
end
def process_page_link(link)
process_page_link_class(link) unless link['name'].nil?
process_page_link_href(link) unless link['href'].nil?
end
def process_page_link_class(link)
link['class'] = '' if link['class'].nil?
link['class'] << ' anchor'
end
def process_page_link_href(link)
link['href'] = link['href'].gsub('../', '../../')
link['href'] = link['href'].gsub('.html', '/')
link['href'] = link['href'].gsub('+', '%2B')
end
def remove_duplicated_title
@contents.css('h1').each(&:remove)
end
def switch_specification_section_table_headers_to_normal_cells
@contents.css('.section-specification th').each do |n|
n.node_name = 'td'
n['class'] = 'specification-title'
end
end
def clean_up_method_titles
# Remove the <code><a> tags inside h3.method-title nodes, strip nbsp and
# add the subsubtitle class.
@contents.css('h3.method-title').each do |n|
method_title = n.at_css('code a')
n.content = method_title.content.gsub(/\A\u00A0+/, '') if method_title
n['class'] = 'subsubtitle method-title'
end
end
def switch_parameter_tables_into_definition_lists
# Change the table node into a definition list
# For each row recover the parameter name and the description, and add
# them to the list as term and definition.
@contents.css('table.argument-def').each do |table|
table.node_name = 'dl'
parameters = table.css('tr').map do |row|
parameter = row.at_css('th.argument-name code')
parameter.node_name = 'em'
dt = Nokogiri::XML::Element.new('dt', table.document)
dt.add_child parameter
definition = row.at_css('td:not(.argument-name)').content
dd = Nokogiri::XML::Element.new('dd', table.document)
dd.children = definition
[dt, dd]
end.flatten(1)
table.children.unlink
parameters.each { |p| table.add_child p }
end
end
def remove_footer
@contents.css('footer').each(&:remove)
end
end
end

View file

@ -0,0 +1,140 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
module Pebble
class SearchMarkdown < Redcarpet::Render::HTML
def initialize()
@contents = Array.new
@sections = []
@section = {
:title => nil,
:contents => []
}
super()
end
def get_contents()
@contents.join(" \n ")
end
def get_sections()
unless @section.nil?
@sections << @section
end
@sections.map do | section |
section[:contents] = section[:contents].join("\n")
section
end
@sections
end
def block_code(code, language)
""
end
def header(text, header_level)
unless @section.nil?
@sections << @section
end
@section = {
:title => text,
:contents => []
}
@contents << text
""
end
def paragraph(text)
@contents << text
@section[:contents] << text
""
end
def codespan(text)
text
end
def image(link, title, alt_text)
""
end
def link(link, title, content)
content
end
def list(contents, type)
@contents << contents
@section[:contents] << contents
""
end
def list_item(text, list_type)
@contents << text
@section[:contents] << text
""
end
def autolink(link, link_type)
link
end
def double_emphasis(text)
text
end
def emphasis(text)
text
end
def linebreak()
""
end
def raw_html(raw_html)
""
end
def triple_emphasis(text)
text
end
def strikethrough(text)
text
end
def superscript(text)
text
end
def underline(text)
text
end
def highlight(text)
text
end
def quote(text)
text
end
def footnote_ref(number)
""
end
end
end

View file

@ -0,0 +1,81 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require 'redcarpet'
require 'nokogiri'
module Pebble
class TocGenerator
def initialize(max_depth=-1)
@max_depth = max_depth
@markdown = TocMarkdown.new()
@redcarpet = Redcarpet::Markdown.new(@markdown,
fenced_code_blocks: true,
autolink: true,
tables: true,
no_intra_emphasis: true,
strikethrough: true,
highlight: true)
end
def generate(content)
@redcarpet.render(content)
page_toc = @markdown.get_toc
toc_array(toc_normalised(page_toc))
end
private
# Convert the ToC array of hashes into an array of array so that it can
# be used in the Liquid template.
def toc_array(array)
array.map { |entry| [ entry[:id], entry[:title], entry[:level] ] }
end
# Normalised the ToC array by ensuring that the smallest level number is 1.
def toc_normalised(array)
min_level = 100
array.each { |entry| min_level = [ min_level, entry[:level] ].min }
level_offset = min_level - 1
array.map { |entry| entry[:level] -= level_offset; entry }.select do |entry|
@max_depth == -1 || entry[:level] <= @max_depth
end
end
end
class TocMarkdown < Redcarpet::Render::HTML
def initialize()
@toc = Array.new
@depth = 0
super()
end
def get_toc()
@toc
end
def header(text, header_level)
text = Nokogiri::HTML(text).text if text.include?('<')
entry = { title: text, id: text.slugize, level: header_level }
@toc << entry
""
end
end
end

View file

@ -0,0 +1,53 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Generates a list page for each blog author.
# The list of authors is in /source/_data/blog_authors.yml
module Jekyll
class AuthorPage < Page
def initialize(site, base, dir, author)
@site = site
@base = base
@dir = dir
@name = author[0] + '/index.html'
self.process(@name)
self.read_yaml(File.join(base, '_layouts'), 'blog/author_page.html')
self.data['posts'] = site.posts.docs.select { |post| post['author'] == author[0] }
self.data['author_name'] = author[1]['name']
self.data['author'] = author[0]
self.data['title'] = "Pebble Developer Blog: #{author[1]['name']}"
end
end
class AuthorPageGenerator < Generator
def generate(site)
if ! site.layouts.key? 'blog/author_page'
throw 'Layout for the blog author pages is missing.'
end
dir = site.config['tag_dir'] || 'blog/authors'
site.data['authors'].each do |author|
author[1]['num_posts'] = site.posts.docs.select { |post| post['author'] == author[0] }.length
site.pages << AuthorPage.new(site, site.source, dir, author)
end
end
end
end

View file

@ -0,0 +1,69 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Generates a list page for each blog tag.
require 'slugize'
module Jekyll
class TagPage < Page
def initialize(site, base, dir, tag)
@site = site
@base = base
@dir = dir
@name = tag[0].slugize + '/index.html'
self.process(@name)
self.read_yaml(File.join(base, '_layouts'), 'blog/tag_page.html')
self.data['posts'] = tag[1].sort_by(&:date).reverse
self.data['name'] = tag[0]
self.data['tag'] = tag[0]
self.data['title'] = "Pebble Developer Blog: #{tag[0]}"
end
end
class TagPageRedirect < Page
def initialize(site, base, dir, tag)
@site = site
@base = base
@dir = dir
@name = tag[0].slugize + '.html'
self.process(@name)
self.read_yaml(File.join(base, '_layouts'), 'utils/redirect_permanent.html')
self.data['path'] = '/' + File.join(dir, tag[0].slugize) + '/'
end
end
class TagPageGenerator < Generator
def generate(site)
if ! site.layouts.key? 'blog/tag_page'
throw 'Layout for the blog tag pages is missing.'
end
dir = site.config['tag_dir'] || 'blog/tags'
site.tags.each do |tag|
site.pages << TagPage.new(site, site.source, dir, tag)
site.pages << TagPageRedirect.new(site, site.source, dir, tag)
end
end
end
end

View file

@ -0,0 +1,50 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
module Jekyll
class EnvironmentGenerator < Generator
priority :highest
def initialize(site)
# TODO: Figure out how to check for the environment type.
require 'dotenv'
Dotenv.load
end
def generate(site)
if !ENV.has_key?('URL') && ENV.has_key?('HEROKU_APP_NAME')
ENV['URL'] = "https://#{ENV['HEROKU_APP_NAME']}.herokuapp.com"
ENV['HTTPS_URL'] = "https://#{ENV['HEROKU_APP_NAME']}.herokuapp.com"
end
site.data['env'].each do |item|
if ENV.has_key?(item['env'])
set_config(site.config, item['config'], ENV[item['env']])
elsif item.has_key?('default')
set_config(site.config, item['config'], item['default'])
end
end
end
private
# TODO: Rewrite this function to allow for nested keys.
def set_config(config, key, value)
config[key] = value
end
end
end

View file

@ -0,0 +1,23 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# FilterAssetify is a Liquid filter to prepend the asset_path when needed
module FilterAssetify
def assetify(input)
asset_path = @context.registers[:site].config['asset_path']
%r{^/[^/]}.match(input) ? (asset_path + input) : input
end
end
Liquid::Template.register_filter(FilterAssetify)

View file

@ -0,0 +1,21 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
module FilterBasename
def basename(input, suffix)
File.basename(input, suffix)
end
end
Liquid::Template.register_filter(FilterBasename)

View file

@ -0,0 +1,28 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
module FilterFakePlatform
def fake_platform(input)
case input
when 'aplite'
'SDK 3'
when 'basalt'
'SDK 4'
else
'??'
end
end
end
Liquid::Template.register_filter(FilterFakePlatform)

View file

@ -0,0 +1,29 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
module FilterHashSort
def hash_sort(hash, property=nil)
return [] if hash.nil?
sorted_hash = []
hash.each { |key, value| sorted_hash << [key, value] }
if property.nil?
sorted_hash.sort! { |a, b| a[0] <=> b[0] }
else
sorted_hash.sort! { |a, b| a[1][property] <=> b[1][property] }
end
sorted_hash
end
end
Liquid::Template.register_filter(FilterHashSort)

View file

@ -0,0 +1,28 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Liquid filter that does magic to make plurals easy.
module Pluralize
def pluralize(number, singular, plural = nil)
if number == 1
"#{number} #{singular}"
elsif plural.nil?
"#{number} #{singular}s"
else
"#{number} #{plural}"
end
end
end
Liquid::Template.register_filter(Pluralize)

View file

@ -0,0 +1,25 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require 'slugize'
# Liquid filter that turns a string into a slug.
# Used to turn tag names into the tag URL part.
module FilterSlugize
def slugize(input)
input.slugize
end
end
Liquid::Template.register_filter(FilterSlugize)

View file

@ -0,0 +1,236 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require 'htmlentities'
require 'algoliasearch'
require 'slugize'
require 'dotenv'
require 'securerandom'
module Jekyll
class GeneratorAlgolia < Generator
# Do this last so everything else has been processed already.
priority :lowest
def initialize(_config)
Dotenv.load
end
def generate(site)
@site = site
return unless check_config?
@prefix = site.config['algolia_prefix'] || ''
@random_code = random_code
Algolia.init(application_id: site.config['algolia_app_id'],
api_key: site.config['algolia_api_key'])
@indexes = setup_indexes
generate_all
end
private
def check_config?
if @site.config['algolia_app_id'].nil? || @site.config['algolia_app_id'].empty?
Jekyll.logger.warn(
'Config Warning:',
'You did not provide a ALGOLIA_APP_ID environment variable.'
)
return false
end
if @site.config['algolia_api_key'].nil? || @site.config['algolia_api_key'].empty?
Jekyll.logger.warn(
'Config Warning:',
'You did not provide a ALGOLIA_API_KEY environment variable.'
)
return false
end
true
end
def generate_all
generate_blog_posts
generate_guides
generate_documentation
generate_none_guide_guides
generate_other
end
def random_code
SecureRandom.hex
end
def setup_indexes
indexes = {}
@site.data['search_indexes'].each do |name, properties|
index = Algolia::Index.new(@prefix + name)
unless properties['settings'].nil?
index.set_settings(properties['settings'])
end
indexes[name] = index
end
indexes
end
def generate_documentation
return if @site.data['docs'].nil?
documents = @site.data['docs'][:symbols].map do |item|
next if item[:language] == 'c_preview'
if item[:summary].nil? || item[:summary].strip.length == 0
Jekyll.logger.warn(
'Search Warning:',
"There was no summary for the symbol '#{item[:name]}' in #{item[:language]}."
)
end
{
'objectID' => item[:url],
'title' => item[:name],
'splitTitle' => item[:name].split(/(?=[A-Z])/).join(' '),
'url' => item[:url],
'summary' => item[:summary],
'kind' => item[:kind],
'language' => item[:language],
'type' => 'documentation',
'ranking' => doc_language_rank[item[:language]] * 1000,
'randomCode' => @random_code
}
end.compact
@indexes['documentation'].save_objects(documents)
end
def generate_blog_posts
documents = []
@site.posts.docs.each do | post |
# Calculate the age of the post so we can prioritise newer posts
# over older ones.
# NOTE: post.date is actually a Time object, despite its name
age = (Time.now - post.date).round
author = post.data['author']
post.get_sections.each do | section |
# Ignore sections without any contents.
if section[:contents].strip.size == 0
next
end
if section[:title].nil?
url = post.url
else
url = post.url + '#' + section[:title].slugize
end
document = {
'objectID' => url,
'title' => post.data['title'],
'sectionTitle' => section[:title],
'url' => url,
'urlDisplay' => post.url,
'author' => author,
'content' => HTMLEntities.new.decode(section[:contents]),
'posted' => post.date,
'age' => age,
'type' => 'blog post',
'randomCode' => @random_code
}
documents << document
end
end
@indexes['blog-posts'].save_objects(documents)
end
def generate_guides
documents = []
return if @site.collections['guides'].nil?
@site.collections['guides'].docs.each do | guide |
group = @site.data['guides'][guide.data['guide_group']]
unless group.nil? || group['subgroups'].nil? || guide.data['guide_subgroup'].nil?
subgroup = group.nil? ? '' : group['subgroups'][guide.data['guide_subgroup']]
end
guide.get_sections.each do | section |
url = guide.url
unless section[:title].nil?
url = url + '#' + section[:title].slugize
end
document = {
'objectID' => url,
'title' => guide.data['title'],
'sectionTitle' => section[:title],
'url' => url,
'urlDisplay' => guide.url,
'content' => HTMLEntities.new.decode(section[:contents]),
'group' => group.nil? ? '' : group['title'],
'subgroup' => subgroup.nil? ? '' : subgroup['title'],
'type' => 'guide',
'randomCode' => @random_code
}
documents << document
end
end
@indexes['guides'].save_objects(documents)
end
def generate_none_guide_guides
documents = []
gs_pages = @site.pages.select { |page| page.data['search_index'] }
gs_pages.each do |page|
page.get_sections.each do |section|
url = page.url
url = url + '#' + section[:title].slugize unless section[:title].nil?
document = {
'objectID' => url,
'title' => page.data['title'],
'sectionTitle' => section[:title],
'url' => url,
'urlDisplay' => page.url,
'content' => HTMLEntities.new.decode(section[:contents]),
'group' => page.data['search_group'],
'subgroup' => page.data['sub_group'],
'type' => 'not-guide',
'randomCode' => @random_code
}
documents << document
end
end
@indexes['guides'].save_objects(documents)
end
def generate_other
documents = @site.data['search-other'].map do |other|
{
'objectID' => other['id'],
'title' => other['title'],
'url' => other['url'],
'content' => other['description'],
'randomCode' => @random_code
}
end
@indexes['other'].save_objects(documents)
end
def doc_language_rank
{
'c' => 10,
'rockyjs' => 9,
'pebblekit_js' => 8,
'pebblekit_android' => 6,
'pebblekit_ios' => 4
}
end
end
end

View file

@ -0,0 +1,166 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require 'open-uri'
require 'zip'
require 'nokogiri'
require_relative '../lib/pebble_documentation_pebblekit_android.rb'
require_relative '../lib/pebble_documentation_c.rb'
require_relative '../lib/pebble_documentation_js.rb'
require_relative '../lib/pebble_documentation_pebblekit_ios.rb'
require_relative '../lib/toc_generator.rb'
OpenURI::Buffer.send :remove_const, 'StringMax' if OpenURI::Buffer.const_defined?('StringMax')
OpenURI::Buffer.const_set 'StringMax', 0
# Master plugins for processing the documentation on the site.
# The actual work is done in individual classes for each language type.
# Each class generates three types of data:
# - Symbols
# - Pages
# - Tree
#
# The Symbols are a list of objects that are things such as methods, classes
# or enums, that are used to populate the search indexes, and power the double
# backtick docs linking.
#
# The Pages are the Jekyll pages that will be part of the built site and are
# what the user will see.
#
# The Tree is used to build the site navigation.
#
# Note: The docs_url variable is created from the environment.
# See environment.md for more information.
module Jekyll
class DocsGenerator < Generator
priority :high
def generate(site)
@site = site
@docs = {
symbols: [],
pages: [],
tree: {}
}
if @site.config['docs_url'].nil? || @site.config['docs_url'].empty?
Jekyll.logger.warn(
'Config Warning:',
'You did not provide a DOCS_URL environment variable.'
)
elsif !@site.config['skip_docs'].nil? && (@site.config['skip_docs'] == 'true')
Jekyll.logger.info('Docs Generation:', 'Skipping documentation generation...')
else
Jekyll.logger.info('Docs Generation:', 'Generating pages...')
generate_docs
render_pages
Jekyll.logger.info('Docs Generation:', 'Done.')
end
set_data
end
private
def generate_docs
# The order of these functions will determine the order of preference
# when looking up symbols e.g. double backticks
# DO NOT CHANGE THE ORDER UNLESS YOU KNOW WHAT YOU ARE DOING
generate_docs_c
generate_docs_c_preview unless @site.data['docs']['c_preview'].nil?
generate_docs_rocky_js
generate_docs_pebblekit_js
generate_docs_pebblekit_android
generate_docs_pebblekit_ios
end
def render_pages
@docs[:pages].each { |page| @site.pages << page }
end
def set_data
# A somewhat ugly hack to let the Markdown parser have access
# to this data.
@site.config[:docs] = @docs
@site.data['docs'] = @docs
# Another ugly hack to make accessing the data much easier from Liquid.
@site.data['docs_tree'] = JSON.parse(JSON.dump(@docs[:tree]))
@site.data['symbols'] = JSON.parse(JSON.dump(@docs[:symbols]))
end
def generate_docs_c
docs = Pebble::DocumentationC.new(
@site,
@site.config['docs_url'] + @site.data['docs']['c'],
'/docs/c/'
)
load_data(docs, :c)
end
def generate_docs_c_preview
docs = Pebble::DocumentationC.new(
@site,
@site.config['docs_url'] + @site.data['docs']['c_preview'],
'/docs/c/preview/',
'c_preview'
)
load_data(docs, :c_preview)
end
def generate_docs_rocky_js
docs = Pebble::DocumentationJs.new(
@site,
@site.data['docs']['rocky_js'],
'/docs/rockyjs/',
'rockyjs',
true
)
load_data(docs, :rockyjs)
end
def generate_docs_pebblekit_js
docs = Pebble::DocumentationJs.new(
@site,
@site.data['docs']['pebblekit_js'],
'/docs/pebblekit-js/',
'pebblekit_js'
)
load_data(docs, :pebblekit_js)
end
def generate_docs_pebblekit_android
docs = Pebble::DocumentationPebbleKitAndroid.new(
@site,
@site.config['docs_url'] + @site.data['docs']['pebblekit_android']
)
load_data(docs, :pebblekit_android)
end
def generate_docs_pebblekit_ios
docs = Pebble::DocumentationPebbleKitIos.new(
@site,
@site.config['docs_url'] + @site.data['docs']['pebblekit_ios'],
'/docs/pebblekit-ios/'
)
load_data(docs, :pebblekit_ios)
end
def load_data(docs, type)
@docs[:tree][type] = []
docs.load_symbols(@docs[:symbols])
docs.create_pages(@docs[:pages])
docs.build_tree(@docs[:tree][type])
end
end
end

View file

@ -0,0 +1,65 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
module Jekyll
class GeneratorExamples < Generator
def initialize(_config)
end
def generate(site)
@site = site
process_tags
process_languages
process_hardware_platforms
@site.data['examples_metadata'] = {
'tags' => @tags,
'languages' => @languages,
'hardware_platforms' => @hardware_platforms
}
end
def process_tags
@tags = {}
@site.data['examples'].each do |example|
next if example['tags'].nil?
example['tags'].each do |tag|
@tags[tag] = { 'count' => 0 } unless @tags.has_key?(tag)
@tags[tag]['count'] += 1
end
end
end
def process_languages
@languages = {}
@site.data['examples'].each do |example|
next if example['languages'].nil?
example['languages'].each do |language|
@languages[language] = { 'count' => 0 } unless @languages.has_key?(language)
@languages[language]['count'] += 1
end
end
end
def process_hardware_platforms
@hardware_platforms = {}
@site.data['examples'].each do |example|
next if example['hardware_platforms'].nil?
example['hardware_platforms'].each do |hw|
@hardware_platforms[hw] = { 'count' => 0 } unless @hardware_platforms.has_key?(hw)
@hardware_platforms[hw]['count'] += 1
end
end
end
end
end

View file

@ -0,0 +1,94 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require 'htmlentities'
require 'algoliasearch'
require 'slugize'
require 'dotenv'
module Jekyll
class GeneratorGuides < Generator
priority :highest
def initialize(config)
end
def generate(site)
@site = site
site.collections['guides'].docs.each do |guide|
group = find_group(guide)
subgroup = find_subgroup(guide, group)
guide.data['group_data'] = group
guide.data['subgroup_data'] = subgroup
unless subgroup.nil?
subgroup['guides'] << {
'title' => guide.data['title'],
'url' => guide.url,
'menu' => guide.data['menu'],
'order' => guide.data['order'],
'summary' => guide.data['description']
}
else
unless group.nil?
group['guides'] << {
'title' => guide.data['title'],
'url' => guide.url,
'menu' => guide.data['menu'],
'order' => guide.data['order'],
'summary' => guide.data['description']
}
end
end
end
site.data['guides'] = [] if site.data['guides'].nil?
site.data['guides'].each do |id, group|
group['url'] = "/guides/#{id}/"
if group['subgroups'].nil?
group['subgroups'] = []
next
end
group['subgroups'].each do |id, subgroup|
subgroup['url'] = "#{group['url']}#{id}/"
end
end
end
def find_group(guide)
@site.data['guides'].each do |id, group|
if id == guide.data['guide_group']
group['guides'] = [] if group['guides'].nil?
return group
end
end
nil
end
def find_subgroup(guide, group)
return if group.nil? || group['subgroups'].nil?
group['subgroups'].each do |id, subgroup|
if id == guide.data['guide_subgroup']
subgroup['guides'] = [] if subgroup['guides'].nil?
return subgroup
end
end
nil
end
end
end

View file

@ -0,0 +1,35 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require 'googlestaticmap'
module Jekyll
class GeneratorMeetups < Generator
def initialize(config)
end
def generate(site)
@site = site
map = GoogleStaticMap.new(:width => 700, :height => 500)
site.data['meetups'].each do |meetup|
map.markers << MapMarker.new(:color => "0x9D49D5FF",
:location => MapLocation.new(:latitude => meetup['pin']['latitude'],
:longitude => meetup['pin']['longitude']
)
)
end
@site.data['meetups_map_url'] = map.url(:auto)
end
end
end

View file

@ -0,0 +1,58 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require 'uglifier'
require 'digest'
module Jekyll
# Jekyll Generator for concatenating and minifying JS for production site
class GeneratorMinifyJS < Generator
priority :highest
def initialize(_)
end
def generate(site)
return if site.config['rack_env'] == 'development'
@site = site
@tmp_dir = File.join(site.source, '../tmp/')
@js_dir = 'assets/js/'
@tmp_js_dir = File.join(@tmp_dir, @js_dir)
libs_js = uglify_libs
libs_md5 = Digest::MD5.hexdigest(libs_js)
@site.data['js']['lib_hash'] = libs_md5
create_libs_js(libs_js, libs_md5)
end
private
def uglify_libs
ugly_libs = []
@site.data['js']['libs'].each do |lib|
lib_path = File.join(@site.source, 'assets', lib['path'])
ugly_libs << Uglifier.compile(File.read(lib_path))
end
ugly_libs.join("\n\n")
end
def create_libs_js(js, md5)
FileUtils.mkdir_p(@tmp_js_dir)
File.open(File.join(@tmp_js_dir, "libs-#{md5}.js"), 'w') do |f|
f.write(js)
end
@site.static_files << Jekyll::StaticFile.new(@site, @tmp_dir, @js_dir,
"libs-#{md5}.js")
end
end
end

View file

@ -0,0 +1,62 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
module Jekyll
class GeneratorRedirects < Generator
priority :lowest
def initialize(config)
end
def generate(site)
@site = site
site.data['redirects'].each do |redirect|
if is_infinite_redirect?(redirect[0], redirect[1])
Jekyll.logger.warn "Redirect Warning:", "Skipping redirect of #{redirect[0]} to #{redirect[1]}"
next
end
@site.pages << RedirectPage.new(@site, @site.source, File.dirname(redirect[0]), File.basename(redirect[0]), redirect[1])
end
end
private
# Returns true if the redirect pair (from, to) will cause an infinite
# redirect.
def is_infinite_redirect?(from, to)
return true if from == to
return true if File.basename(from) == 'index.html' && File.dirname(from) == File.dirname(to + 'index.html')
false
end
end
class RedirectPage < Page
def initialize(site, base, dir, name, redirect_to)
@site = site
@base = base
@dir = dir
@name = name.empty? ? 'index.html' : name
self.process(@name)
self.read_yaml(File.join(base, '_layouts', 'utils'), 'redirect_permanent.html')
self.data['redirect_to'] = redirect_to
end
end
end

View file

@ -0,0 +1,54 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require 'redcarpet'
require_relative '../lib/search_markdown'
module Jekyll
module Convertible
def get_output
process_search
@search_markdown.get_contents
end
def get_sections
process_search
@search_markdown.get_sections
end
private
def process_search
unless @search_markdown.nil?
return
end
@search_markdown = Pebble::SearchMarkdown.new()
redcarpet = Redcarpet::Markdown.new(@search_markdown,
fenced_code_blocks: true,
autolink: true,
tables: true,
no_intra_emphasis: true,
strikethrough: true,
highlight: true)
payload = {}
info = { :filters => [Jekyll::Filters], :registers => { :site => site, :page => payload['page'] } }
raw_content = render_liquid(content, payload, info, '.')
redcarpet.render(raw_content)
end
end
end

View file

@ -0,0 +1,85 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require_relative '../lib/toc_generator'
module Jekyll
class Document
include Convertible
alias_method :parent_to_liquid, :to_liquid
def to_liquid
Utils.deep_merge_hashes parent_to_liquid, {
'toc' => toc,
'related_docs' => related_docs
}
end
private
def toc
unless @toc
generate_toc if data['generate_toc']
end
(@toc.nil? || @toc.empty?) ? nil : @toc
end
def related_docs
# Skip the warning, we don't want docs or links to them
if !@site.config['skip_docs'].nil? && (@site.config['skip_docs'] == 'true')
return
end
return nil if data['related_docs'].nil?
docs = data['related_docs'].map do | doc |
# Use existing doc data if it exists
if !doc.nil? and doc.is_a?(Hash) and doc.has_key?("name") and doc.has_key?("url")
doc
else
# use nil if data is formated in an unexpected way
if doc.nil? or !doc.is_a? String
next
else
# Otherwise search for the symbol
symbol = @site.config[:docs][:symbols].find do |symbol|
symbol[:name].downcase == doc.downcase
end
if symbol.nil?
Jekyll.logger.warn "Related Warning:", "Could not find symbol '#{doc}' in '#{data['title']}'"
next
else
{
'name' => symbol[:name],
'url' => symbol[:url],
}
end
end
end
end
end
def generate_toc
generator = Pebble::TocGenerator.new(data['toc_max_depth'] || -1)
@toc = generator.generate(content)
end
end
end

View file

@ -0,0 +1,45 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require_relative '../lib/toc_generator'
# Overriding the Jekyll Page class to do magic
module Jekyll
class Page
def to_liquid(attrs = ATTRIBUTES_FOR_LIQUID)
super(attrs + %w[
toc
])
end
private
def toc
unless @toc
generate_toc if data['generate_toc']
end
(@toc.nil? || @toc.empty?) ? nil : @toc
end
def generate_toc
generator = Pebble::TocGenerator.new(data['toc_max_depth'] || -1)
@toc = generator.generate(content)
end
end
end

View file

@ -0,0 +1,45 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require_relative '../lib/toc_generator'
# Overriding the Jekyll Page class to do magic
module Jekyll
class Post
def to_liquid(attrs = ATTRIBUTES_FOR_LIQUID)
super(attrs + %w[
toc
])
end
private
def toc
unless @toc
generate_toc
end
(@toc.nil? || @toc.empty?) ? nil : @toc
end
def generate_toc
generator = Pebble::TocGenerator.new(data['toc_max_depth'] || -1)
@toc = generator.generate(content)
end
end
end

View file

@ -0,0 +1,323 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require 'redcarpet'
require 'pygments'
require 'slugize'
require 'nokogiri'
module Jekyll
module Converters
# Jekyll Markdown Converter wrapper for Redcarpet using the PebbleMarkdown
# HTML render class.
class Markdown::PebbleMarkdownParser
def initialize(config)
@site_config = config
end
def convert(content)
content = '' if content.nil?
pbl_md = PebbleMarkdown.new(@site_config)
Redcarpet::Markdown.new(pbl_md,
fenced_code_blocks: true,
autolink: true,
tables: true,
no_intra_emphasis: true,
strikethrough: true,
highlight: true).render(content)
end
# Redcarpet HTML render class to handle the extra functionality.
class PebbleMarkdown < Redcarpet::Render::HTML
def initialize(config)
@site_config = config
super()
end
def preprocess(document)
process_links_with_double_backticks(document)
process_double_backticks(document)
document
end
# Add ID and anchors to all headers.
def header(text, header_level)
if text.include?('<')
id = Nokogiri::HTML(text).text.slugize
else
id = text.slugize
end
str = "<h#{header_level} id=\"#{id}\" class=\"anchor\">"
str += text
str += "</h#{header_level}>"
str
end
def paragraph(text)
if (match = /^\^(CP|LC)\^/.match(text))
"<p class=\"platform-specific\" data-sdk-platform=\"#{shortcode_to_platform(match[1])}\">#{text[(match[1].length + 2)..-1].strip}</p>"
else
"<p>#{text}</p>"
end
end
# Use Pygments to generate the syntax highlighting markup.
def block_code(code, language)
classes = ['highlight']
if /^nc\|/.match(language)
classes << 'no-copy'
language = language[3..-1]
end
if language == 'text'
"<div class=\"#{classes.join(' ')}\"><pre>#{code}</pre></div>"
else
set_classes(Pygments.highlight(code, lexer: language), classes)
end
end
def link(url, title, content)
if content == 'EMBED'
embed(url)
else
classes = []
if /^DOCS:/.match(title)
title = title[5..-1]
classes << 'link--docs'
end
# URL begins with a single slash (but not double slash)
url = baseurl + url if %r{^/[^/]}.match(url)
data_str = ''
if (match = regex_button.match(content))
classes << 'btn'
classes << 'btn--markdown'
classes.concat(match[3].split(',').map { |cls| 'btn--' + cls })
content = match[1]
end
if (match = regex_link_data.match(title))
match[3].split(',').each do |item|
item = item.split(':')
data_str += ' data-' + item[0] + '="' + item[1] + '"'
end
title = match[1]
end
"<a href=\"#{url}\"" \
" title=\"#{title}\"" \
" class=\"#{classes.join(' ')}\"#{data_str}>#{content}</a>"
end
end
# Better image handling.
# * Add size specificiations (taken from RDiscount)
# * Prepend the site baselink to images that beings with /
# TODO: Handle the cases where image link begins with //
# TODO: Maybe add additional style choices (centered, inline, etc)
def image(link, title, alt_text)
if (size_match = /^(.*)\ =([0-9]+)x?([0-9]*)$/.match(link))
link = size_match[1]
width = size_match[2]
height = size_match[3]
end
classes = []
if (match = regex_button.match(alt_text))
classes.concat(match[3].split(','))
alt_text = match[1]
end
link = asset_path + link if %r{^/[^/]}.match(link)
img_str = "<img src=\"#{link}\""
img_str += " title=\"#{title}\"" unless title.to_s.empty?
img_str += " alt=\"#{alt_text}\"" unless alt_text.to_s.empty?
img_str += " width=\"#{width}\"" unless width.to_s.empty?
img_str += " height=\"#{height}\"" unless height.to_s.empty?
img_str += " class=\"#{classes.join(' ')}\"" unless classes.empty?
img_str += ' />'
img_str
end
private
# This is used to process links that contain double backticks.
# For example:
# [click me](``Window``)
# This allows for the text of a link to be different than the name
# of the symbol you are linking to.
def process_links_with_double_backticks(document)
# Skip the warning, we don't want docs or links to them
if !@site_config['skip_docs'].nil? && (@site_config['skip_docs'] == 'true')
return
end
document.gsub!(/(\[([^\]]+)\])\(``([^`]+)``\)/) do |str|
url = Regexp.last_match[3]
text = Regexp.last_match[2]
text_in_brackets = Regexp.last_match[1]
language, symbol = parse_symbol(url)
entry = docs_lookup(symbol, language)
Jekyll.logger.warn('Backtick Warning:', "Could not find symbol '#{text}'") if entry.nil?
entry ? (text_in_brackets + "#{backtick_link(entry)}") : text
end
end
def process_double_backticks(document)
# Skip the warning, we don't want docs or links to them
if !@site_config['skip_docs'].nil? && (@site_config['skip_docs'] == 'true')
return
end
document.gsub!(/([^`]+|\A)``([^`]+)``/) do |str|
language, symbol = parse_symbol(Regexp.last_match[2])
entry = docs_lookup(symbol, language)
if entry.nil?
Jekyll.logger.warn('Backtick Warning:', "Could not find symbol '#{Regexp.last_match[2]}'")
language.nil? ? str : ('``' + Regexp.last_match[2][language.size+1..-1] + '``')
else
symbol_str = Regexp.last_match[2]
symbol_str = symbol_str[language.size+1..-1] unless language.nil?
"#{Regexp.last_match[1]}[`#{symbol_str}`]#{backtick_link(entry)}"
end
end
end
def backtick_link(symbol)
"(#{symbol[:url]} \"DOCS:#{symbol[:name]}\")"
end
# Split the documentation string into language and symbol name.
def parse_symbol(str)
match = /^([a-z]*:)?([A-Za-z0-9_:\.\ ]*)/.match(str)
language = match[1]
language = language[0..-2].downcase unless language.nil?
name = match[2]
return language, name
end
def docs_lookup(name, language)
return nil if name.nil?
@site_config[:docs][:symbols].find do |symbol|
symbol[:name].downcase == name.downcase &&
(language.nil? ? true : symbol[:language] == language)
end
end
def embed(url)
if (match = regex_youtube_video.match(url))
youtube(match[2])
elsif (match = regex_youtube_playlist.match(url))
youtube_playlist(match[3])
elsif (match = regex_vimeo_video.match(url))
vimeo(match[1])
elsif (match = regex_slideshare.match(url))
slideshare(match[1])
elsif (match = regex_gist.match(url))
gist(match[2])
end
end
def set_classes(html, classes)
doc = Nokogiri::HTML::DocumentFragment.parse(html)
doc.child['class'] = classes.join(' ')
doc.to_html
end
def youtube(id)
'<div class="embed embed--youtube"><div class="video-wrapper">' \
'<iframe width="640" height="360" frameborder="0" allowfullscreen' \
" src=\"//www.youtube.com/embed/#{id}?rel=0\" ></iframe>" \
'</div></div>'
end
def youtube_playlist(id)
'<div class="embed embed--youtube"><div class="video-wrapper">' \
'<iframe frameborder="0" allowfullscreen'\
" src=\"//www.youtube.com/embed/videoseries?list=#{id}\" ></iframe>" \
'</div></div>'
end
def vimeo(id)
'<div class="embed embed--vimeo"><div class="video-wrapper">' \
'<iframe width="500" height="281" frameborder="0"' \
' webkitallowfullscreen mozallowfullscreen allowfullscreen' \
" src=\"//player.vimeo.com/video/#{id}\"></iframe>" \
'</div></div>'
end
def slideshare(id)
'<div style="width: 100%; height: 0px; position: relative; padding-bottom: 63%;">' \
"<iframe src=\"https://www.slideshare.net/slideshow/embed_code/key/#{id}\"" \
' frameborder="0" allowfullscreen style="width: 100%; height: 100%; position: absolute;">' \
'</iframe>'\
'</div>'
end
def gist(id)
'<div class="embed embed--gist">' \
"<script src=\"//gist.github.com/#{id}.js\"></script>" \
'</div>'
end
def baseurl
@site_config['baseurl'] || ''
end
def asset_path
@site_config['asset_path'] || ''
end
def link_sdk(url, title, content)
end
def regex_youtube_video
%r{youtube\.com/(watch\?v=|v/|embed/)([a-z0-9A-Z\-_]*)}
end
def regex_youtube_playlist
%r{^(https?://)?([w]{3}\.)?youtube\.com/playlist\?list=([a-z0-9A-Z\-]*)}
end
def regex_vimeo_video
%r{vimeo.com/video/([0-9]+)}
end
def regex_slideshare
%r{slideshare.net/slideshow/embed_code/key/([a-z0-9A-Z]*)}
end
def regex_gist
%r{^(https?://)?gist.github\.com/(.*)}
end
def regex_button
/^(.*)\ (&gt;|>)\{?([a-z,0-9\-]*)\}?$/
end
def regex_link_data
/^(.*)\ (&gt;|>)\{([a-z\-_,:0-9A-Z]+)\}$/
end
def shortcode_to_platform(shortcode)
platforms = {
'CP' => 'cloudpebble',
'LC' => 'local'
}
platforms[shortcode]
end
end
end
end
end

View file

@ -0,0 +1,42 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
module Jekyll
class AlertBlock < Liquid::Block
alias_method :render_block, :render
def initialize(tag_name, text, tokens)
super
@type = text.strip
end
def render(context)
site = context.registers[:site]
converter = site.find_converter_instance(::Jekyll::Converters::Markdown)
content = converter.convert(render_block(context))
if @type == "important"
return "<div class=\"alert alert--fg-white alert--bg-dark-red\">" << "<strong>Important</strong><br/>" << "#{content}" << "</div>"
end
if @type == "notice"
return "<div class=\"alert alert--fg-white alert--bg-purple\">" << "<strong>Notice</strong><br/>" << "#{content}" << "</div>"
end
Jekyll.logger.error "Liquid Error:", "Alert type '#{@type}' is not valid. Use 'important' or 'notice'."
return ''
end
end
end
Liquid::Template.register_tag('alert', Jekyll::AlertBlock)

View file

@ -0,0 +1,31 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Liquid tag for including a style tag.
class TagAssetCss < Liquid::Tag
def initialize(tag_name, text, tokens)
super
@text = text
end
def render(context)
style = context[@text]
site = context.registers[:site]
unless %r{^//}.match(style)
style = "#{site.config['asset_path']}/css/#{style}.css"
end
"<link rel=\"stylesheet\" type=\"text/css\" href=\"#{style}\">"
end
end
Liquid::Template.register_tag('asset_css', TagAssetCss)

View file

@ -0,0 +1,31 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Liquid tag for including a script tag.
class TagAssetJs < Liquid::Tag
def initialize(tag_name, text, tokens)
super
@text = text
end
def render(context)
script = context[@text]
site = context.registers[:site]
unless %r{^//}.match(script)
script = "#{site.config['asset_path']}/js/#{script}.js"
end
"<script type=\"text/javascript\" src=\"#{script}\"></script>"
end
end
Liquid::Template.register_tag('asset_js', TagAssetJs)

View file

@ -0,0 +1,35 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Creates a link to a blog post author page and uses their full name when known.
class TagAuthorLink < Liquid::Tag
def initialize(tag_name, text, tokens)
super
@text = text
end
def render(context)
author_name = context[@text]
site = context.registers[:site]
author = site.data['authors'][author_name]
if author
url = "#{site.baseurl}/blog/authors/#{author_name}/"
"<a href=\"#{url}\">#{author['name']}</a>"
else
author_name
end
end
end
Liquid::Template.register_tag('author_link', TagAuthorLink)

View file

@ -0,0 +1,36 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
class TagAuthorPhoto < Liquid::Tag
def initialize(tag_name, text, tokens)
super
pieces = text.split(' ')
@name = pieces[0]
@size = pieces[1].to_i
end
def render(context)
author_name = context[@name]
site = context.registers[:site]
author = site.data['authors'][author_name]
unless author && author['photo']
author = site.data['authors']['pebble']
end
photo = author['photo']
photo = site.config['asset_path'] + photo if %r{^/[^/]}.match(photo)
"<img src=\"#{photo}\" width=#{@size} height=#{@size}>"
end
end
Liquid::Template.register_tag('author_photo', TagAuthorPhoto)

View file

@ -0,0 +1,58 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Liquid inline tag to produce an Edit Gist in CloudPebble button
class TagCloudPebbleEditGist < Liquid::Tag
def initialize(tag_name, gist_id, tokens)
super
@gist_id = gist_id.strip
end
def render(context)
page = context.registers[:page]
extension = File.extname(page['name'])
if extension == '.md'
render_markdown
else
render_html
end
end
def render_markdown
"[#{content} >{#{markdown_classes}}](#{url})"
end
def render_html
"<a href=\"#{url}\" title=\"\" class=\"#{html_classes}\">#{content}</a>"
end
private
def url
"https://cloudpebble.net/ide/gist/#{@gist_id}"
end
def html_classes
'btn btn--wide btn--pink'
end
def markdown_classes
'wide,pink'
end
def content
'Edit in CloudPebble'
end
end
Liquid::Template.register_tag('cloudpebble_edit_gist', TagCloudPebbleEditGist)

View file

@ -0,0 +1,43 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Liquid tag that displays the names of everyone who contributed on the file.
class TagGitContributors < Liquid::Tag
def initialize(tag_name, text, tokens)
super
@text = text
end
def render(context)
list = '<ul class="git-contributors">'
contributors(context).each do |name|
list += "<li>#{name}</li>" unless name.empty?
end
list += '</ul>'
list
end
private
def contributors(context)
site = context.registers[:site]
page = context[@text]
file_path = page['relative_path'] || page['path']
full_path = './' + site.config['source'] + file_path
names = `git log --follow --format='%aN |' "#{full_path}" | sort -u`
names.split('|').map { |name| name.strip }
end
end
Liquid::Template.register_tag('git_contributors', TagGitContributors)

View file

@ -0,0 +1,67 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
class TagGuideLink < Liquid::Tag
def initialize(tag_name, text, tokens)
super
@text = text.strip
end
def make_html(title, url)
return "<a href=\"#{url}\"><em>#{title}</em></a>"
end
def render(context)
guide_path = @text.split('#')[0].strip
guide_hash = (@text.split('#').length > 1 ? @text.split('#')[1] : '').strip
if guide_hash.length > 1
guide_hash = (guide_hash.split(' ')[0]).strip
end
# Custom title?
guide_title = nil
index = @text.index('"')
if index != nil
guide_title = (@text.split('"')[1]).strip
guide_title = guide_title.gsub('"', '')
guide_path = guide_path.split(' ')[0]
end
site = context.registers[:site]
site.collections['guides'].docs.each do |guide|
path = guide.relative_path.sub(/^_guides\//, '').sub(/\.md$/, '')
# Check if it's a 'section/guide' path
if path.index('/') != nil
if path == guide_path
return make_html(guide_title != nil ? guide_title : guide.data['title'],
"#{guide.url}#{guide_hash == '' ? '' : "##{guide_hash}"}")
end
end
# Check if it's a 'section' path
site.data['guides'].each do |id, group|
if id == guide_path
return make_html(guide_title != nil ? guide_title : group['title'], "/guides/#{guide_path}")
end
end
end
# No match
Jekyll.logger.error "Liquid Error:", "Could not find the guide or section for #{@text}."
return ''
end
end
Liquid::Template.register_tag('guide_link', TagGuideLink)

View file

@ -0,0 +1,35 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require 'pygments'
# Liquid block tag to run code through Pygments syntax highlighter.
class Highlight < Liquid::Block
def initialize(tag_name, markup, tokens)
super
options = JSON.parse(markup)
return unless options
@language = options['language']
@classes = options['classes']
@options = options['options'] || {}
end
def render(context)
str = Pygments.highlight(super.strip, lexer: @language, options: @options)
str.gsub!(/<div class="highlight"><pre>/,
"<div class=\"highlight #{@classes}\"><pre>")
str
end
end
Liquid::Template.register_tag('highlight', Highlight)

View file

@ -0,0 +1,38 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
class TagIfStartsWith < Liquid::Block
def initialize(tag_name, text, tokens)
super
matches = /^([A-Za-z\.\_\-\/]+) ([A-Za-z\.\_\-\/\']+)$/.match(text.strip)
@pieces = {
:outer => matches[1],
:inner => matches[2]
}
@text = text
end
def render(context)
outer = context[@pieces[:outer]]
inner = context[@pieces[:inner]]
if inner.nil? || outer.nil?
return ""
end
if outer.downcase.start_with?(inner.downcase)
return super.to_s.strip
end
""
end
end
Liquid::Template.register_tag('if_starts_with', TagIfStartsWith)

View file

@ -0,0 +1,30 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
class TagImportJs < Liquid::Tag
def initialize(tag_name, text, tokens)
super
@text = text
end
def render(context)
site = context.registers[:site]
filename = File.join(site.source, "_js", @text).strip
File.read(filename)
end
end
Liquid::Template.register_tag('import_js', TagImportJs)

View file

@ -0,0 +1,35 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
module Jekyll
class MarkdownBlock < Liquid::Block
alias_method :render_block, :render
def initialize(tag_name, markup, tokens)
super
end
# Uses the default Jekyll markdown parser to
# parse the contents of this block
#
def render(context)
site = context.registers[:site]
converter = site.find_converter_instance(::Jekyll::Converters::Markdown)
converter.convert(render_block(context))
end
end
end
Liquid::Template.register_tag('markdown', Jekyll::MarkdownBlock)

View file

@ -0,0 +1,39 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
module Jekyll
class PlatformBlock < Liquid::Block
alias_method :render_block, :render
def initialize(tag_name, text, tokens)
super
@platform = text.strip
end
def render(context)
if (@platform == "local") || (@platform == "cloudpebble")
site = context.registers[:site]
converter = site.find_converter_instance(::Jekyll::Converters::Markdown)
content = converter.convert(super)
return "<div class=\"platform-specific\" data-sdk-platform=\"#{@platform}\">#{content}</div>"
end
Jekyll.logger.error "Liquid Error:", "Platform '#{@platform}' is not valid. Use 'local' or 'cloudpebble'."
return ''
end
end
end
Liquid::Template.register_tag('platform', Jekyll::PlatformBlock)

View file

@ -0,0 +1,50 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require 'json'
class TagScreenshotViewer < Liquid::Block
def render(context)
site = context.registers[:site]
data = JSON.parse(super)
viewer_html = '<div class="screenshot-viewer">'
viewer_html += '<div class="screenshot-viewer__tabs js-screenshot-tabs">'
data['platforms'].each do |platform|
viewer_html += "<h4 data-platform=\"#{platform['hw']}\">#{platform['hw']}</h4>"
end
viewer_html += '</div>'
viewer_html += '<div class="screenshot-viewer__screenshots">'
data['platforms'].each do |platform|
viewer_html += "<div class=\"screenshot-viewer__platform\" data-platform=\"#{platform['hw']}\">"
image_url = make_image_url(data, platform)
viewer_html += "<img src=\"#{site.config['asset_path']}#{image_url}\" class=\"pebble-screenshot pebble-screenshot--#{platform['wrapper']}\" />"
viewer_html += '</div>'
end
viewer_html += '</div>'
viewer_html += '</div>'
viewer_html
end
private
def make_image_url(data, platform)
File.dirname(data['image']) + '/' + File.basename(data['image'], File.extname(data['image'])) +
"~#{platform['hw']}" + File.extname(data['image'])
end
end
Liquid::Template.register_tag('screenshot_viewer', TagScreenshotViewer)

View file

@ -0,0 +1,18 @@
#!/bin/bash
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
documentation build ./js-docs/rocky -f json > source/_data/jsdocs-rocky.json
documentation build ./js-docs/pkjs -f json > source/_data/jsdocs-pkjs.json

View file

@ -0,0 +1,19 @@
#!/bin/bash
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Updates the compiled Handlebars templates file from the source templates.
handlebars source/_js/templates/*.tpl -f source/assets/js/templates.js -e tpl

33
devsite/scripts/video-encode.sh Executable file
View file

@ -0,0 +1,33 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
SOURCE=$1;
DIR=$(dirname "$SOURCE")
FILENAME=$(basename "$SOURCE")
EXT="${FILENAME##*.}"
FILENAME="${FILENAME%.*}"
if [ -z "$1" ]
then
echo "\nUsage: $0 <MP4_FILE>\n"
exit 1;
fi
echo "Creating OGV file..."
ffmpeg -i $SOURCE -loglevel panic -q 5 -pix_fmt yuv420p -acodec libvorbis -vcodec libtheora $DIR/$FILENAME.ogv;
echo "Creating WEBM file..."
ffmpeg -i $SOURCE -loglevel panic -c:v libvpx -c:a libvorbis -pix_fmt yuv420p -quality good -b:v 2M -crf 5 -vf "scale=trunc(in_w/2)*2:trunc(in_h/2)*2" $DIR/$FILENAME.webm;
echo "Creating PNG poster..."
ffmpeg -i $SOURCE -loglevel panic -f image2 -ss 0 -vframes 1 $DIR/$FILENAME.png;
echo "Done!"

51
devsite/source/404.html Normal file
View file

@ -0,0 +1,51 @@
---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
layout: default
title: Oops! 404, Page not found
footer: true
error404: true
scripts:
- '404'
menu_section: none
---
<div class="row full-height" style="overflow: hidden;">
<div class="col-s-4 text-center hidden-xs hidden-s">
<img src="{{ site.asset_path }}/images/404-pebble.png" style="margin: -50% 50px 0;">
</div>
<div class="col-s-8">
<div class="container">
<div class="row">
<div class="col-l-12">
<h1 class="page-title">404 Page Not Found</h1>
<p>
Sorry, we couldn't find the page you were looking for.
</p>
<p>
If you clicked a link on the site and it led you here, we would
appreciate you <a href="/contact/?type=404">letting us know that we
broke something.</a>
</p>
<p id="js-404-search-intro" style="display: none;">
Below are some suggested pages that might be helpful in finding what
you were trying to find!
</p>
<ul id="js-404-search"></ul>
</div>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,42 @@
---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
permalink: /feed.xml
title: Pebble SDK 2.0 BETA0 - Changelog
date: 2013-11-01
---
This version is a preview of what will be publicly released soon as a BETA. This means that it is the last time we introduce large changes to the APIs, they will be much more stable in the future.
It includes some last significant changes that will impact every application.
* We have changed the format of the `wscript` file. **You must update your wscript file.** The easiest way to do this is to generate a new project with `pebble new-project` and use the generated `wscript`.
* Header files `pebble_os.h`, `pebble_app.h` and `pebble_fonts.h` are replaced by `pebble.h`
* `click_config_provider()` signature has changed and instead of filling a struct, you call `window_*_click_subscribe`. Please refer to the [Migration Guide](/guides/migration/).
* On AppMessage:
* We have changed the signature of most AppMessage functions. Please refer to the [Migration Guide](/guides/migration/).
* We have added functions to query the size of the AppMessage buffers. They still return the same value that in previous versions ... for now.
* We have added a [Mobile Developer Guide](/guides/communication/) covering PebbleKit iOS and Android. Please take a look at them, they should answer lots of questions.
* [PebbleKit Android Documentation](/guides/communication/using-pebblekit-android) is now available on the website and in the SDK `Documentation` folder.
* We have done a lot of work on PebbleKit JavaScript:
* The [documentation](/guides/communication/using-pebblekit-js) describes the new model for loading and stopping JavaScript apps. You should take a look.
* On Android only (for now) apps will automatically start when they get a message from Pebble.
* On Android only (for now) you can use the gear icon to open a configuration window on the phone.
* You can now call `Pebble.addEventListener` instead of `PebbleEventListener.addEventListener`
* DataLogging is now supported on Android, iOS6 and iOS7
* And of course we have fixed a large quantities of bugs.
This is a private release under NDA.

View file

@ -0,0 +1,74 @@
---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
title: Pebble SDK 2.0 BETA1 - Changelog
date: 2013-11-06
---
* Pebble SDK 2.0 is currently in BETA and intended for developers only.
* SDK 2.0 will be released later this year as an over-the-air update to all Pebble users.
* Applications written for Pebble 1.x are not compatible with SDK 2.0
* ANCS Notifications (aka BLE notifications) are not supported for iOS users in this version
Updates:
* 2013 11 07: Added a firmware for watch with serial number starting with a 'Q' (aka hardware 1.5)
## What has changed since BETA0
### User Bug Fixes and Feature Enhancements
- Fixed crashing bugs on the iOS app. Users should experience improved stability.
- New iOS users no longer need to manage access to their address book in order to see Caller ID on their Pebble.
- The iOS app does not overflow the banner bar (at the top of the screen) on iOS7
- The Pebble now can show >80 unread notifications, up from 8 previously.
- Backlight is triggered on a tap from any of the 6 axes of the watch
- Android app stability has been improved
- On Android, switching orientation while updating firmware does not stop the firmware update
- The music app now stays open rather than switching back to the menu after 1 minute
### Known User Issues
- The status indication button in the main screen sometimes repeatedly throbs green then red, repeatedly.
- (iOS7 users, iPhone4S and higher) If you select "Enable Notifications" and select the Cancel button in the system alert that comes up, it can take up to 30 seconds for the iOS app to allow selection of "Enable Notifications" again. As a workaround, if you launched this screen from the Status screen, you can hit the up arrow, then the red "Not receiving notifications" button, and retry enabling notifications again.
- In certain conditions if you enable and disable Airplane mode on your Pebble, you may need to restart the Pebble iOS app completely in order to re-enable notifications again
- On Android, you may need to restart the Pebble app after installing a new version of a JavaScript app to ensure that your changes are successfully loaded.
- On Android, use of HTML5 local storage does not guarantee data will be saved across sessions.
- Duplicate APP_LOG messages can be received while using the pebble tool; these are intermittent and developers should use timestamps to identify duplicates
- If there is not enough app heap remaining, some essential functions that allocate on that heap will fail, such as system fonts or persistent storage
- The iOS app can sometimes crash when opening a PBW file if it is not already running
### Developer Bug Fixes and Enhancements (Major Feature Enhancements are covered in the SDK)
- Apps now only need one Pebble specific header, pebble.h
- Exiting an app showing no windows will now not crash the Pebble
- Pebble will not crash when cancelling an already cancelled timer
- Pebble will not crash when cancelling an unregistered timer
- Holding the up or down buttons now cause repeated clicks in menus
- Changed the default stroke color to Black instead of White, as the default background color is White
- Apps now cannot overwrite the system memory, and will be terminated if they attempt to
- Int type changes on many APIs to ensure future compatability
- User data can be attached to a window
- The pebble tool displays an error message if you try to install an application that is not compatible with the target firmware
- The menu icon resource is displayed even if it is not the first resource
- Libpebble times out if no apps are installed
- The valid range for UUIDs has changed - see the developer documentation
- The Android app now installs bundles in Gmail attachments
- System fonts now show capital W, Q and O
- `pebble Install`` will now install even if the Android app is left in the “Update” screen
- Apps will not crash if a text layer is not large enough to hold the requested text
- `pebble install --logs` proceeds to tail logs even when install fails
- Apps will not crash when popping/removing already popped/removed windows from the stack

View file

@ -0,0 +1,97 @@
---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
title: Pebble SDK 2.0 BETA2 - Changelog
date: 2013-11-14
---
* Pebble SDK 2.0 is currently in BETA and intended for developers only.
* SDK 2.0 will be released later this year as an over-the-air update to all Pebble users.
* Applications written for Pebble 1.x are not compatible with SDK 2.0
## What has changed since BETA1
Overview:
- We have included ANCS in 2.0 - iOS users will get all notifications
- We have added a screenshot tool
- We have increased the AppMessage buffer size for PebbleKit JS Apps
- We have changed a few firmware APIs to always pass parameter in this order: (buffer, size)
- We have fixed many bugs
Known problems and bugs:
- We are still working actively on improving datalogging on iOS and Android. If you wish to use this framework, please get in touch with us and tell us about your experience.
- JavaScript apps on Android will only run if the phone is turned on and the Pebble app running (the easiest way to check this is to bring it to the foreground). This will be fixed soon.
- If you downloaded the SDK before 5pm PST on 2013-11-14, your API documentation is probably broken. We have fixed this and pushed a new release without updating the version number because there are absolutely no changes (except the doc is now there ;).
### Firmware
- Added support for ANCS
- Fix UI bug when getting phone calls
- Improved address book lookups when getting phone calls
- Changed the behaviour when an app is closed from PebbleKit: return to the last running app or watchface (instead of the launcher)
- Show malloc and free in the generated documentation
- Fix doc for AccelAxisType
- Do not animation a window disappearing if the window was pushed without animation
- Add `GCornersRight` in the documentation of `GCornerMask`
- Document `GTextOverflowMode`
- Document the return value of the `persist_*` functions
- Document `AppTimerCallback`
- When exiting an app, all unload handlers will be called for loaded windows
- Changed the order of parameters for `persist_read_data()`, `persist_read_string()`, `persist_write_data()`, `dict_calc_buffer_size()`, `dict_serialize_tuplets_to_buffer_with_iter()`: always ask for the pointer first and then the count or size
- Fix bug where the status bar would not be displayed properly
- Enabled Accelerometer high resolution output
- Automatically reset the accelerometer when app exits
- Removed the 1Hz accelerometer settings because it breaks the shake to backlight - Use peek() instead if you only need one sample per second.
- Updated the guaranteed minimum buffer sizes for appmessage. They are in fact 124 / 636.
- Fix bug where appLaunch commands would not be ACK'd
- Increased AppMessage buffer sizes for JavaScript apps: they get 2k in and out.
### iOS App
- Fixed several dataLogging bugs
- Fixed most common crashes reported by TestFlight
### Android App
- Fixed several dataLogging bugs
- Fixed most common crashes reported by TestFlight
### PebbleKit iOS
- DataLogging apps do not need to include an `appInfo.json` file anymore
- Use `setAppUUID` to give the UUID of the app you want to talk to
### PebbleKit Android
- Add `getWatchFWVersion()` to get a `FirmwareVersionInfo` object
- Add `isDataLoggingSupported`
### SDK Tools
- Added a `screenshot` command to the `pebble` tool
- Revert the change in the tool where we would enforce a specific range of uuids
- Improved error messages when the tools cannot be found
- Do not truncate log messages coming from the JavaScript console
- Only log app_log (and not system log) by default. Use `--verbose` to get all the logs.
### Examples
- Fix a bug in the dataspooling demo where sealions and pelicans got mixed up
- Fix PebbleKit Examples for the new `setAppUUID` style
- Fix examples to use the new parameter orders for `persist` functions

View file

@ -0,0 +1,117 @@
---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
title: Pebble SDK 2.0 BETA3 - Changelog
date: 2013-12-12
---
* Pebble SDK 2.0 is currently in BETA and intended for developers only.
* SDK 2.0 will be released later this year as an over-the-air update to all Pebble users.
* Applications written for Pebble 1.x are not compatible with SDK 2.0
* If a 2.0 version of an application exists, the Pebble mobile applications will automatically install it when when a user upgrades her/his Pebble.
## What has changed since BETA2
Overview:
- The Android app fixes a large number of JS-related bugs.
- The Android app fixes a bug where all messages sent to android would be automatically acknowledged. Your application should acknowledge app messages.
- Some new user features in the firmware: Notification settings (with Do Not Disturb), better Alarms
- Lots of small UI and stability fixes in Pebble.
Known problems and bugs:
### Firmware
- added a Notification menu in the Settings to disable Notifications and configure a DoNotDisturb time frame
- much better Alarm app with a nicer UI, snooze support, disabled alarms support
- fix bugs where incoming calls could cause the vibration to stay on continuously
- fix a rare condition where the accelerometer would crash if an interrupt comes too late or the accelerometer sent 0 samples
- fix accelerometer behaviour when only 1 sample is requested: only send one sample (instead of 2)
- fix a bug where an iOS device could disconnect just 1-2 seconds after connecting
- automatically reconnect when user leaves Airplane Mode
- show (in settings) that vibrations are disabled when Pebble is plugged
- improved the set date/time UI to use the new DateTime UI (as in Alarms)
- adjust the framebuffer offset for modal window transitions
- reduced BLE connection interval
- log more information when an application crashes
- do not crash if an app_message section is re-opened - display warning instead
- fix a bug which caused firmware updates to fail under some conditions (mostly android)
- appsync will only update a dictionary if it has enough memory to do so (instead of finding out half-way that it does not have enough memory)
- always return to the launcher after an app crash (to avoid a crash loop on a watchface)
- *_destroy() will accept NULL pointers without errors
- always go back to the top of the menu when launching the menu from a watchface (to make "blind" navigation easier)
- fix a bug where an actionbar button would still be "pressed"
- show Bluetooth Name on getting started screen
- automatically delete old apps that are not compatible with this firmware version
- accelerate scrolling on the menu
- use modal window icon in the status bar as long as the modal window is displayed
- Export dict_size so external developers don't have to do pointer math :)
- fix a bug where scrolling in a long list of notifications could cause the display to "bounce"
- fix a bug where lots of app logging messages could overflow the system task queue and kill app message
- API documentation completely reviewed and updated
- missed call modal views will timeout after 180s
- force quit app when the back button is held for 2 seconds
- menu_cell_basic_draw() now automatically center the text in the cell. If you do not want a subtitle, pass NULL (otherwise, space will be reserved for the subtitle).
- fixed some bluetooth settings to avoid duplicated messages (could cause screenshot to go over 100%, duplicated log entries, firmware upgrade to fail, etc)
- `peek()`ing the accelerometer is not allowed anymore when you have also subscribed to receive updates
- fix a bug where the accelerometer would get stuck after a few hours
### iOS App
- fix a bug where datalogging could dump messages to another phone on your local network
- fix a bug where datalogging would get into a deadlock
- fix a bug where the developer connection would appear active but would be closed
### Android App
- fix a number of cases where a JS app would not be launched
- fix bug where clicking the configure icon would not open the configuration view of an app
- fix a bug which caused every AppMessage sent to Android to be acknowledged by the system
- Select the Google Play Music App as the default music player
- fix support email to use the Pebble bluetooth name instead of the last four digits of the serial
- if there is an error when uploading an app, do not dismiss the update screen right away
- do not dump large logs if stats json is not found
- check for firmware update when foregrounded
- fix bug where a canceled app install would be reported as completed
- fix bug where an install would fail silently because the resources could not be loaded
- display specific error message when a user tries to install a 2.0 app on a 1.x Pebble
- fix a bug where the android app would display error message "Could not update" while looking for updates in the background
### PebbleKit iOS
- allow one iOS application to exchange messages with several Pebble apps (with different UUIDs)
- fix a crash trying to parse invalid firmware version
- add CocoaPods support (see pebblekit-ios readme for more info)
- enabled "all warnings" and fixed errors
### PebbleKit Android
No changes.
### SDK Tools
- added support to upload any bundle (including firmware)
- added test to detect missing tools
- better implementation of the --debug flag
- fix bug where tools would fail when installed in a folder with a space in it
- fix bug where tools would fail on project with a space in the name
- some 1.x to 2.x conversion bugs fixed
- automatically re-enable applog when the watch reconnects
### Examples
- fix crashing bugs in 91Dub

View file

@ -0,0 +1,102 @@
---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
title: Pebble SDK 2.0 BETA4 - Changelog
date: 2013-12-23
---
* Pebble SDK 2.0 is currently in BETA and intended for developers only.
* SDK 2.0 will be released early next year as an over-the-air update to all Pebble users.
* Applications written for Pebble 1.x are not compatible with SDK 2.0
* If a 2.0 version of an application exists, the Pebble mobile applications will automatically install it when when a user upgrades her/his Pebble.
**You can start uploading your application to the Pebble appstore TODAY - [Please do so](http://dev-portal.getpebble.com/)!**
## What has changed since BETA3
Overview:
- Fixed a problem where the iOS app would get killed after a few minutes in the background
- Lots of Data Logging fixes on the firmware and on Android
- Added timestamps on accelerometer samples
- Improved error handling for PebbleKit JS apps on iOS and Android
### Firmware
- Developers of apps can now register single and multi click handlers on the back button
- Holding down the back button for 1.5s will force quit an existing app
- Fixed bugs and optimize the filesystem: faster persist times, less problems with persistent storage, fix a bunch of rather complex problems where the recovery firmware could be damaged
- Fixed scroll offset bug when displaying notifications
- Dismiss missed call notfication after 180s
- Fixed a bug where unicode characters were not supported in appinfo.json
- Changed graphics_text_layout_get_max_used_size() to _not_ require a graphic context
- Fixed a few more bluetooth bugs
- Fixed a bug where Pebble could crash when you delete the last alarm
- Fixed memory read exception that can occur when using a malloc-ed appsync buffer
- Save notifications to storage during do not disturb
- Document AccelAxisType in API Documentation
- Fixed Music UI problems
- Automatically center on screen a selected cell in a SimpleMenuLayer
- Fixed bug where snprintf could crash the watch
- Display an error message if a 2.0 pebble is connected to a 1.x mobile app
- Fixed a bug where calling atoi() would crash an app
- Many DataLogging improvements and fixes based on new unit tests
- Display an alert on Pebble when we reset due to a system crash
- Ignore NULL pointer passed to text_layer_destroy()
- Limit the number of datalogging sessions to 20
- Fixed a race condition that occurs when you set the sampling rate immediately after subscribing to the accel service
- Keep persistent storage intact when upgrading an application
- Added timestamps on accelerometer samples and a flag indicating if the watch was vibrating at the time of the sample
- Fixed a bug where psleep could crash pebble
- Fixed a bug where text_layer could add ellipsis to the wrong line
### iOS App
- Fixed a bug where the iOS app would get killed in the background after just a few minutes
- Show a local notification if a developer is connected but the app is about to get killed
- PebbleKit JS: Fixed a bug where apps could not access localStorage with the array syntax
- PebbleKit JS: Fixed a bug where a space in an URL opened with XmlHTTPRequest could crash the iOS app
- PebbleKit JS: Fixed a bug where sending a byte array with 0xff in it would send 0x00 instead
### Android App
- PebbleKit JS: Fixed a bug where a byte array would not be sent properly for named keys
- Use new Android KitKat (4.4) APIs to do pairing on 4.4
- PebbleKit JS: Do not send ack for ack/nack messages
- Fixed Android crashing with OutOfMemory error when using Data Logging
- Fixed Android Data Logging of byte array that was not working
### PebbleKit iOS
- Do not ack ACKs...
### PebbleKit Android
- No changes
### SDK Tools
- Added support to libpebble to trigger reboot to recovery firmware
- Added support for computers where python2 and python3 co-exist
- Fixed an exception when receiving APP_LOG with extended characters
- Fixed a bug where unicode characters were not supported in characterRegex field of `appinfo.json`
- Fixed 30 second delay that can occur when building pebble apps on Ubuntu when there is no internet access
- Added Pillow python dependency: needed for the screenshot functionality
- Detect PIL/Pillow conflict and suggest a fix to the user
### Examples
- Added a License to the examples

View file

@ -0,0 +1,112 @@
---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
title: Pebble SDK 2.0 BETA5 - Changelog
date: 2014-01-10
---
* Pebble SDK 2.0 is currently in BETA and intended for developers only.
* Applications written for Pebble 1.x are not compatible with SDK 2.0
* If a 2.0 version of an application exists, the Pebble mobile applications will automatically install it when when a user upgrades her/his Pebble.
**You can start uploading your application to the Pebble appstore TODAY - [Please do so](http://dev-portal.getpebble.com/)!**
## What has changed since BETA4
Overview:
- Fixed Android datalogging bugs where data would get duplicated
- Merged datalogging fixes for iOS that were supposed to be in BETA4 (sorry)
- Added an end of session message on Android datalogging
- Fixed accelerometer bugs where the accelerometer would stop sending data
- Changed the animation when switching from one watchface to the next ...
- Changed the battery API to return values going up to 100%
### Known Problems and limitations
* **Accelerometer locking up**: Although we have fixed several bugs around the accelerometer, we have noticed a few instance of the accelerometer locking up and the accel callback not being called anymore. If you see this happen again, please use the "Contact Support" button to send us logs. Make sure you change the subject to "Accelerometer lockup". Thank you very much!
* `getAccountToken()` (in PebbleKit JS) is not working yet. It currently returns a random string. In an upcoming update (before 2.0) it will return a unique token linked to the Pebble user account.
This is tied with appstore functionnalities and not available yet in this beta build.
* Some crash due to internal timers and deadlock conditions are still being investigated.
* This version will reset your persistent storage when you install it
### Changes for Firmware:
- Added a script in the SDK to help analyze app memory usage (analyze_static_memory_usage)
- Changed the animation between watchfaces
- Fix various composition bugs during animations
- Several fix to the Pebble filesystem to fix problems occuring in persistent storage and datalogging
- Add `bitmap_layer_get_bitmap()`
- s/1 minutes/1 minute/ in the alarm app
- Do not crash when loading a font from a NULL resource (can happen when memory is tight!)
- Ignore buttons while animating from one window to another
- Fix the back button in the getting started
- Fix simplicity to show the time immediately
- Fix sliding text to animate the time in immediately
- Change simplicity to load the fonts as system fonts
- Invert modal window status bar icons
- Reworked `gpath_draw_filled()` to use less memory and use heap instead of stack
- Improve persistent storage behaviour under tight memory
- Enforce file size limits
- Improve number of sectors of the filesystem
- Fix a bug where in some condition going up and down after installing a watchface would not return to it
- Fix a bug where `text_layer_get_content_size()` could return values that caused the text to be truncated
- Do not crash in `gpath_draw_filled()` if called with 0 points
- Added event service unsubscribe for app_focus_event (fixes a crash for Glance and Upright)
- Changed the battery API to return values going up to 100%
### Changes for Pebble iOS App:
- Fixes to datalogging to avoid duplicated data and iOS app getting stuck
### Changes for Pebble Android App:
- Added an intent sent when a data logging session is finished
- Fix a problem where JavaScript would not start on android 4.0
- Fix some bluetooth scanning bugs that could cause timeouts or pebbles not detected
- Improved bluetooth pairing screens for various Android versions
### Changes for PebbleKit iOS:
- Fix some threading/deadlock bugs
### Changes for PebbleKit Android:
- Do not retransmit same datalogging blocks more than once
- Add a callback when the datalogging session is finished
### Changes for SDK Tools:
- Added command `pebble analyze-size` to dump sections and symbol sizes
- Increase timeout of the wsclient (could be triggered when installing firmware)
- Added `--simple` option to `pebble new-project` to create a minimalist app
- Updated to websocket-client 1.12 and removed dependency to io_sock
### Changes for Examples:
- Update classio-battery-connection example to peek() the bluetooth connection status at startup
### Changes for Documentation:
- Updated JS configuration example
- Added link to the pebble-hacks/configurable project in the JS doc
- Removed reference to the 1Hz acc sampling rate (RIP)
- Added an example use of the `pebble install` command in the example page
- Updated the `app_focus_subscribe` documentation in the event guide
- Added a note in the datalogging guide to mention it's not a realtime system
- Added doc for `only_shown_on_communication` in the anatomy of a pebble app chapter
- Added that you can call `app_message_outbox_begin` in `outbox_sent` and `outbox_failed` now
- Fixed formatting of the appinfo.json example in the anatomy of a pebble app chapter

View file

@ -0,0 +1,92 @@
---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
title: Pebble SDK 2.0 BETA6 - Changelog
date: 2014-01-17
---
* Pebble SDK 2.0 is currently in BETA and intended for developers only.
* Applications written for Pebble 1.x are not compatible with SDK 2.0
* If a 2.0 version of an application exists, the Pebble mobile applications will automatically install it when when a user upgrades her/his Pebble.
> **IMPORTANT NOTES FOR iOS Users**:
>
> * You must delete the Pebble app on your phone before installing this new version. It will now be called "Pebble Dev" and not "Pebble.". You must also re-install all of your JavaScript apps after installing this new version.
>
> * iPhone5S, iPad Air and Retina iPad Mini users will need to manually pair in the **Settings** of the phone.
## What has changed since BETA5
Overview:
- The iOS Application distributed with BETA6 includes the new Pebble appstore
- The firmware fixes a number of hard to reproduce crashes with system timers. This will fix a lot of the "Dangerously rebooting" Pebble crashes.
### Known Problems and limitations
* `getAccountToken()` (in PebbleKit JS) is not working yet. It currently returns a random string. In an upcoming update (before 2.0) it will return a unique token linked to the Pebble user account.
This is tied with appstore functionnalities and not available yet in this beta build.
* The bugs that were reported on datalogging-iOS on BETA5 are not fixed yet in this release
### Changes for Firmware:
* Rework the system timer to fix all timer related crashes
* Add support for Pebble Steel LED to show charging status
* Round rather than floor the battery charge percentage
* Reverted timings for stm32 for 64MHz system clock based on stable 16Hz SPI clock. Fixes display flicker at 30Hz, as well as saving power at the lower system clock (80->64) and sleeping more often due to faster display updates.
* Fix a crash when canceling the bluetooth pairing dialog
* Fix a bug where pushing a window in a window_unload callback would cause a crash
* Export AccelData structure in the API doc
* Vibrate when an app or watchface is installed
* Fix a bug where the phone modal window would not update properly
* Fix the light threshold for Pebble Steel
### Changes for Pebble iOS App:
* Added the Pebble appstore
* Added support for In-App Notifications
* Add support for migrating 1.x apps into 2.0 apps
* Fix a bug where the iOS app could crash when you switch away from a JavaScript app that has an ongoing network connection
* PebbleKit JS iOS: sendAppMessage() now returns a transaction id
### Changes for Pebble Android App:
* No changes.
### Changes for PebbleKit iOS:
* add isNewer convenience call to PBWatch+Version
* move NSJSONSerialization helper to PebbleVendor
* add isEqualVersionOnly to just compare version number components, ignoring timestamp & hash
### Changes for PebbleKit Android:
* No changes.
### Changes for SDK Tools:
* Fix spelling in an error message (s/Insure/Ensure/)
### Changes for Examples:
* No changes.
### Changes for Documentation:
* Fix a 404 on the pebble tool link in the JS guide
* Fix the persistence guide to reflect the new standardized parameters orders
* Fix a typo in the title of the UI framework guide
* Added designer resources in the UX design chapter

View file

@ -0,0 +1,145 @@
---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
title: Pebble SDK 2.0 BETA7 - Changelog
date: 2014-01-23
---
Pebble SDK 2.0 is still in BETA and is **recommended only** for developers working on new applications for the upcoming Pebble appstore.
## Update Jan 31st: 2.0 Release Candidate 3
We fixed two more crashes. iOS user will automatically get the update. Android users can download it from this site.
Not sending an email to everyone this time because it really is a small changes and we want to spare your inbox before the week-end.
## Update Jan 30th: 2.0 Release Candidate 2
On January 30th, we released a 2.0 Release Candidate version of the firmware with the following changes:
* fixes a number of crashes
* app no longer gets killed when it cancels an invalid timer
* removes “persist_raw -9” message
* low battery message always uses the right icon
* fixes crash on watch shutdown
* fixes crash when using accel
## Updated Jan 29th: 2.0 Release Candidate
On January 29th, we released a 2.0 Release Candidate version of the firmware with the following changes:
* Fixed numerous crashes
* %Z flag passed to strftime no longer crashes the watch
* Fixed iOS connected but not receiving anything issue
* Firmware will now delete all data logging data on factory reset
* Rate limit logging to prevent apps from crashing app with logging loops
* Fixed issues were buttons become unresponsive
* Fixed gpath getting clipped in some cases
* Fixed accel lockup issue
* Fixed accel not using the right sampling rate
* Added low battery warning
* Cancel snooze timer when alarm is deleted
Please continue using BETA7 versions of the SDK and mobile applications.
## What has changed since BETA6
Overview:
- More random crashes fixed in the firmware
- Seriously improved datalogging on iOS (and some bugfixes on Android)
- Fixed the URL scheme to install Pebble applications. It did not work in Beta6.
- Added support for `getAccountToken()` in PebbleKit JS (iOS only at the moment)
- iOS application and PebbleKit iOS are now 64 bits compatible
- iOS application does not crash on iPhone 4 anymore
- Some breaking changes in PebbleKit iOS: We cannot use NSNumber categories in 64 bit because their size is unknown. We added a new PBNumber class. This class is returned if you use the NSNumber Pebble category.
### Known Problems and limitations
- Android does not include the Pebble appstore yet
- PebbleKit iOS apps may see error messages about parsing firmware in their logs. This will be removed soon and does not impact anything in PebbleKit iOS.
### Changes for Firmware:
- fix bugs with modal windows over fullscreen apps
- fix bugs where action bar buttons could get "stuck"
- reduced the power used by Pebble Steel LEDs
- fix some data logging corruption issues on Pebble
- fix a bug where the time of a notification would not be displayed properly
- adjusted the battery charged thresholds so that Pebble Steel turns green when apps show 100%
- fix a bug where you could get 110% battery
- fix a bug where datalogging session could be incompletely initialized when pushed
- fix a bug that could happen when looking for notifications
- fix a bug where some original Pebbles (ev2_4) would never hit 100% battery
- fix infinite loop if you push a modal while one is closing
- fix some button problems on Pebble Steel
### Changes for Pebble iOS App:
- added support for 64bits compilation
- fix a bug where 64 bit devices would not display the bluetooth accessory picker
- native login / signup screen
- fix some button sizes to display text properly
- calculate the area of the buttons on the left menu to highlight them dynamically
- data logging: do not print error messages for partially fetched data - unless we are actually done
- fix some bugs around the Bluetooth accessory picker
- better management of the screens stack in onboarding process. allows users to go back.
- do not display icon for watchfaces in the my pebble screen
- fix bug where appstore url-scheme would not work
- add link to terms and conditions
- deal with timeout errors while installing apps
- downloading apps in the Caches directory instead of Documents since that one gets pruned automatically by the system (Fixes pebblekit#39)
- only allow to start dragging the center view if you start dragging from the left edge
- fix crash for iPhone 4 users
- fix bug where datalogging would try to send data to the Pebble app (instead of 3rd party apps)
- lazy loading the web appstore views to improve loading speed
- sort apps alphabetically in the locker
- memory optimization to stay in the background longer
- getAccountToken() now working in JavaScript
- fix a bug where the configuration view was not sometimes not dismissed
- rename the "Done" button of the configuration view to "Cancel"
- send empty string back to the JS if the user cancels the configuration view (as per documentation)
- only show the "notifications not set up" if there's a watch connected
- fix a JS bug where in some conditions the 'showConfiguration' event might be fired before the 'ready' event
### Changes for Pebble Android App:
- Datalogging: if a session contains bad data, just remove it at startup
### Changes for PebbleKit iOS:
- PebbleKit iOS is now 64 bits (armv7s) compatible
- We cannot use NSNumber categories in 64 bit because their size is unknown. We added a new PBNumber class. This class is returned if you use the NSNumber Pebble category.
- Do not start the datalogging server if appUUID is all zeros
### Changes for PebbleKit Android:
No changes.
### Changes for SDK Tools:
No changes.
### Changes for Examples:
- Classio-battery-connection is a watchface
- Onthebutton is a watchface
- Rumbletime is a watchface
- Fuzzy Time is a watchface
- Changed example UUID's to avoid appstore collisions
### Changes for Documentation:
- Add note about datalogging size

View file

@ -0,0 +1,113 @@
---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
title: Pebble SDK 2.0 DP2 - Changelog
date: 2013-09-24
---
>2.0 DP2.1
> We do not like making releases twice a week but we really wanted to fix the iOS/PebbleKit JS bug and so here it is. PebbleKit JS can now receive message on iOS and on Android. You only need to update your SDK for this to work. We will not release new versions of the mobile apps.
>The rest of the DP2 release notice below still applies.
### Known problems and bugs
- Data Logging does not work on iOS7
- The watch can run out of memory if the applications do not release the memory properly
- On iOS, PebbleKit JS can not receive app messages (fixed in the DP2.1 release)
### In a nutshell
* The format of applications has changed. Every application now requires an `appinfo.json` file in its base directory. This file contains the UUID, name and resources of the application. For PebbleKit JS apps, it may also contain the keys used for app_message communication.
* The `pb-sdk` tool is gone. It is replaced by the `pebble` command line.
* Instead of having the phone connect to the developer box, the developer tools will connect to the mobile application. When you turn on developer mode on your phone, it will display the IP address that you should use to connect to the phone. You can set a `PEBBLE_PHONE` environment variable to avoid retyping this all the time.
* Fixed most blocking bugs reported on Developer Preview 1 (details below)
* The Developer Guide has been completely rewritten and also includes a migration guide.
### Pebble Firmware
- Fixed a bug where launching an application through the bluetooth protocol would cause the app to be re-launched if it was already running.
- Added support for the middle button in the Golf app. This will send a message that is received by PebbleKit on iOS and Android.
- Tap event is now disabled during a vibration (to avoid triggering the event)
- Bumped firmware version and added tests to make sure that old apps will not run on the new firmware and vice-versa
- Fixed various issue with firmware updates
- AppLog does not need to be enabled manually anymore. It is automatically enabled by the `pebble` tool.
### Pebble SDK
- Finalized conversion to new dynamic memory model: all _init functions have been replaced by _create() equivalent that return a pointer to a newly allocated and initialized structure (gbitmap_init functions, gpath_init, property_animation_init and app_sync_init have been updated to the new style)
- Trigger a battery event when the percentage of battery charge changes (will trigger every 2%)
- Data Spooling now takes a `void*` pointer (to avoid useless casting in developer code)
- Data spooling session ids are now random
- persist_read_int now returns 0 if the key does not exist (as per documentation)
- Global static variables were not initialized properly
- Fix a dataspooling bug where sometimes the close message did not contain the correct session id
- Added a bluetooth_connection_service_peek() function
- Export atol/atoi functions
- Export app_comm_get_sniff_interval
- As a developer, I can call the atan()/atan2() function to compute an arc-tangent
- Renamed DataSpooling into DataLogging
- Defined a new Design Pattern to subclass layers and included an example based on the famous Progress Bar layer (`watchapps/feature_layer_data`)
### PebbleKit iOS/Android
- Redesigned completely the Android API for Data Logging (ex data spooling)
### PebbleKit JavaScript
- Fixed a bug where only single digit integers would be passed as integers
- On Android, the apps are now available in the "Webapps" menu
- On iOS and Android, applications will keep on running once they are started (but they do not start automatically yet)
- On iOS, you need to tap the appname to run it.
- On iOS, to allow for `openURL` to work, you need to open the javascript console first
- On iOS and Android, javascript `console.log` are sent over the developer connection (available with `pebble logs`)
- You can now send array of bytes by passing an array of integers to `sendAppMessage()`
- The keys in the appinfo.json file is now optional and you can use integers in strings as keys
- If a received message as a key that is not listed in the `appKeys` block, the integer will be converted to a string
- A bug where the navigation stack could be corrupted when calling `openURL` is fixed
### pb-sdk
- Renamed pb-sdk into pebble
- Added a --version option
- Added a command to clean current project
- Added an example of using APPLOG in the default generated project
- Return meaningful error codes to the shell
- Fixed a bug where `pb-sdk list` would fail if no apps are installed
- Added a --javascript option to `pb-sdk new-project` to create a template of JS code and automatically generate the appinfo.json
- Automatically detect old projects, refuse to build and offer the option to update the project
- Added `convert-project` command to update old projects to the new style (note: this will not update the source code)
- Added clean error message if a resource is missing
### iOS Application
- The developer preview iOS application will automatically offer new version of the beta firmware
- Added support for middle button in golf app
- Some iOS7 fixes
- Switched to TestFlight for distribution and crash reports
### Android Application
- The developer preview Android application will automatically offer new version of the beta firmware
- Added support for middle button in golf app
- Switched to TestFlight for distribution and crash reports
### Pebble SDK Examples
- Added an example for the persistent storage APIs
- Fixed all the iOS examples to build out of the box
- Reworked the Ocean Data Survey example

View file

@ -0,0 +1,122 @@
---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
title: Pebble SDK 2.0 DP3 - Changelog
date: 2013-10-21
---
This version brings some major improvements and a lot of bugfixes. In a nutshell, the big changes are:
* PebbleKit JavaScript now supports geolocation on all platforms
* Pebble supports the ANCS protocol. See details below.
This is a private release under NDA.
### Pebble and ANCS
Pebble has been working on integrating Bluetooth Low Energy (BLE) technology into our upcoming software releases. The initial goal for this work is to greatly enhance the notification experience between a Pebble and a BLE-capable iOS7 device (the iPhone4S and later) - this leverages the "ANCS" notification feature of iOS7. A requirement for the public release of BLE-capable Pebble SW is that it will not change the Android experience. We will work on enhancing the BLE experience specifically for Android users in future SW releases.
If you wish to help Pebble test BLE and ANCS, please read this carefully, this is pre-release software and there are still areas of the experience we are actively enhancing. We greatly appreciate your help in testing these important features :-)
Pebble SDK DP3 (and up) include BLEs capabilities. Download the firmware and mobile apps as instructed in the installation instructions. You do not need anything else.
To configure ANCS and BLE:
- If you already had email configured in the iOS Pebble app, go into the Pebble app and turn that OFF. With ANCS, email notifications will automatically mirror the notifications that show up on your phone.
- The first time you set this up (after you install BLE/ANCS firmware) you will need to pair your phone with the watch to make the BTLE connection.
- On the watch, go into the "Settings" view, and select "Bluetooth".
- On your iOS7 iPhone go into the "Settings" app, select "Bluetooth".
- You should see an entry called "Pebble-LExxxx" where xxxx is the 4 digit code that is shown at the top of the Pebble's Bluetooth screen. Select that entry, and confirm pairing.
- Ensure that BOTH traditional Bluetooth and BLE are paired. You will not be able to perform all of the functions (such as handling phone calls) if the Bluetooth-Classic connection is not working.
- We are actively working on enhancements to pairing, so this process will change as we near public release.
Known issues:
- Only pairing from iOS BT Settings works for now. In-Pebble-app pairing is still TODO.
- If you have a red bar "Notifications require additional setup" in your iOS app, this will not disappear when LE is paired / ANCS is activated. You can safely ignore it.
- Gmail/IMAP in Pebble app + ANCS = Duplicate emails. We recommend turning off email accounts from the Pebble iOS app.
- "Forget" from the watch's BT settings menu doesn't work as expected. iDevice immediately reconnects again.
- All notifications have same icon
- Max message length is shorter than Bluetooth Classic.
- Impact on battery life: we are actively characterizing and working on this, but it is currently less than Bluetooth-Classic only.
Please report any bugs by email to: [ancsbug@getpebble.com](mailto:ancsbug@getpebble.com)!
**Remember, this release is under NDA. Please do not share word of this new feature. Thanks a lot!**
### Known problems and bugs
* Data Logging still does not work on iOS7
* On iOS, to try the "openURL()" function, you must first click the "Details Indicator" button on the table view that lists the JavaScript process
* On Android, to upgrade an existing JavaScript app, you must first kill it in the "JS App Processes" view (look for the Skull And Bones button)
* On some Android phones running 4.1, we have encountered a situation where location services were not working. This problem and the appropriate fix is described by Google [in this forum post](http://productforums.google.com/forum/#!msg/mobile/LEPcl9e3dYE/3LZEhiWACigJ).
### Pebble Firmware
- Fix a bug where Pebble would keep vibrating after answering a call
### Pebble SDK
- Fix a bug which caused all apps to share the same persistent storage file
### PebbleKit iOS/Android
- Removed some deprecated/private APIs call from PebbleKit-iOS
- Update PebbleKit-iOS project files to Xcode 5
- Fix PebbleKit Android build - moved libraries to libs/
### PebbleKit JavaScript
- Getting current location now works on iOS and Android. It is also possible to watch the current position and be notified when it changes.
- Fixed a bug on iOS where sending multiple digits number would not work
- Fixed a bug with the 2.1 release on Android where it would be impossible to use AppMessage (with PebbleKit JS)
- Receiving byte arrays now also works on iOS
- Added Pebble.getAccountToken() to get a unique token for the current user account (Note: this is not documented yet.)
### pebble tool
- Do not return 0 if something bad happened
- Display the footprint of the app in RAM and the available heap space
### Pebble iOS Application
- Fixed a UI bug on iOS7 when deleting an app
- Fixed the Developer Mode UI on iOS7
#### 2013 10 25 - 2.0-DP3.1
We have release a 3.1 update for the iOS application which should fix the most common crash for the app.
### Pebble Android application
- Fixed a bug where the Android app would continuously try to connect to the Pebble even after disconnecting/unpairing
- Fixed a bug where Facebook notifications would have duplicated content in the name field and the main field
- Automatically start an app after installation
- Fixed a bug where it would be impossible to skip the onboarding process
- Fixed a bug where switching the device orientation during firmware upgrade would cause the upgrade to start again
### Pebble SDK Examples
- Fixed a crash in dropzone
- Improved the weatherjs example to use geolocation and display the name of the city
- Added a very cool arcade game to demonstrate use of persistent storage (watchapps/pebble_arcade)
### Documentation
- Simplified installation instructions for Linux
- Fixed a lot of broken links
- Added a chapter on iOS whitelisting
- Added a chapter on fonts
- Reworked most of the developer guides

View file

@ -0,0 +1,79 @@
---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
title: Pebble SDK 2.0.0 - Changelog
date: 2014-02-03
---
This is the first public release of Pebble SDK 2.0 and Pebble firmware 2.0.
## What has changed since BETA7
Overview:
* We have fixed various crashes in the firmware (this was pre-released as 2.0-RC, 2.0-RC2 and 2.0-RC3)
* We have restored support for direct Bluetooth connection from the computer to the pebble in the `pebble` tool
* PebbleKit iOS now includes armv7s, arm64 and x86_64 libraries - There is a known bug in PebbleKit iOS 2.0.0 that can cause your application to crash when it is in the background. Please do not use this version to submit an application to Apple.
## Known bugs and issues
* DataLogging disabled
Pebble iOS 2.0.0 app can enter a crashloop situation when corrupted datalogging bytes are received from Pebble. To avoid this problem, we have disabled the datalogging APIs in firmware 2.0.0. We will re-enable datalogging when the iOS app 2.0.1 is available on the App Store.
* PebbleKit iOS 2.0.0
Can cause 3rd party applications to crash when it is in the background. Please do not use this version to submit an application to Apple. This will be fixed in 2.0.1.
### Changes for Firmware:
Changes since 2.0-RC3:
* fix a deadlock when sending datalogging information
* remove the "Your Pebble has reset" message
The changes between 2.0-BETA7 and 2.0-RC3 were:
* fixes a number of crashes
* app no longer gets killed when it cancels an invalid timer
* removes “persist_raw -9” message
* low battery message always uses the right icon
* fixes crash on watch shutdown
* fixes crash when using accel
### Changes for PebbleKit iOS:
* Updated our version of CocoaLumberJack to fix a crash that could happen when logging in the background
* Updated the build script to actually produce armv7s, arm64 and x86_64 dynamic libraries
* Improve the datalogging protocol (between PebbleApp and PebbleKit) to be more efficient
### Changes for PebbleKit Android:
No changes.
### Changes for SDK Tools:
* We have restored support for direct Bluetooth connection from the computer to the pebble in the `pebble` tool
* Better handling of timeout errors with the websockets
### Changes for Examples:
No changes.
### Changes for Documentation:
* Add parameter `did_vibrate` to AccelData and explanation.
* Add parameter `timestamp` to AccelData and explanation.

View file

@ -0,0 +1,60 @@
---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
title: Pebble SDK 2.0.1 - Changelog
date: 2014-02-20
---
This is a minor update to the Pebble SDK and the Pebble firmware.
## What has changed since 2.0.0
Overview:
- We have re-enabled data logging in the Pebble firmware
## Known bugs and issues
* PebbleKit iOS 2.0.0
Can cause 3rd party applications to crash when it is in the background. Please do not use this version to submit an application to Apple. This will be fixed in a later release.
## Detailed list of changes
### Changes for Firmware:
* We have re-enabled the data logging APIs.
### Changes for PebbleKit iOS:
* No changes.
### Changes for PebbleKit Android:
* No changes.
### Changes for SDK Tools:
* No changes.
### Changes for Examples:
* Reduce the accel discs step time
### Changes for Documentation:
* Updated documentation on Android intents
* Updated documentation on AppMessage, AppSync, Dictionary, Typlets

View file

@ -0,0 +1,63 @@
---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
title: Pebble SDK 2.0.2 - Changelog
date: 2014-03-18
---
This is another minor update to the Pebble SDK and the Pebble firmware.
## What has changed since 2.0.1
Overview:
- Fixes issue that prevented some users from being able to upgrade to 2.0.
- Support for XCode 5.1
- Removed Pillow as dependency for the SDK
## Known bugs and issues
None.
## Detailed list of changes
### Changes for Firmware:
* Fix a bug that could prevent installation fo the firmware
### Changes for PebbleKit iOS:
* No changes.
### Changes for PebbleKit Android:
* No changes.
### Changes for SDK Tools:
* LibPebble upgrade to remove PIL dependency
* replaced PIL with pypng for taking screenshots
### Changes for Examples:
* Update the todolist example to use graphics_text_layout_get_content_size instead of graphics_text_layout_get_max_used_size
* Port improvements to simplicity from firmware to examples
* Update quotes app for ready event
### Changes for Documentation:
* Fixed error in API docs for accel
* Fix javascript close URL in the javascript doc

View file

@ -0,0 +1,70 @@
---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
title: Pebble SDK 2.1.1 - Changelog
date: 2014-05-08
---
## What has changed since 2.1
This release fixes a bug which caused the `pebble` tool to throw an exception when a Pebble app crashed. This is the only fix and we are not releasing a firmware 2.1.1, only the SDK is updated.
## What has changed since SDK 2.0.2
Overview:
* Pebble dynamic memory allocation has been improved and will now detect when you try to free() memory twice.
* With Pebble 2.1 your application will be killed and a message is shown in the console so you can detect and fix this problem, instead of potentially causing a memory corruption issue.
* IMPORTANT: You will need to update your Pebble to run apps built with the 2.1 SDK. Applications compiled with the SDK 2.1 will not appear in the menu and will not run on Pebble firmware 2.0.
## Detailed List of Changes:
### Changes for Firmware:
* Fixed crash caused by calling number_window_set_label
* Fixed white line at the bottom of MenuLayer when last row is selected
* Fixed an issue where the watch would get into a reset loop after boot
* Fixed issue that sometimes caused persistent storage values to not persist
* Fixed issue where caller ID shows info from the previous call
* Fixed caller ID sometimes not displaying on outgoing calls
* Pebble dynamic memory allocation has been improved. Your application will now be killed when you try to free() memory twice
* Apps can no longer crash the watch on app exit
* Bluetooth reconnection is more reliable
* Battery monitor is more consistent
* Multiple power reduction improvements
* Documentation improvements
* Clip text instead of truncating when vertical space is inadequate
* Notifications can be cleared via the Notification section in the Settings menu
### Changes for PebbleKit iOS:
* Some improvements to datalogging to help troubleshoot issues
### Changes for PebbleKit Android:
* No changes
### Changes for SDK Tools:
* Allow firmware bundles to be installed with the install command
* Allow SDK location to be overridden by the `PEBBLE_SDK_PATH` environment variable
* Replaced PIL with pypng for taking screenshots
* Fixed extra row always being added to screenshots
### Changes for Examples:
* Removed ToDoList demo from SDK examples
### Changes for Documentation:
* Various documentation fixes and improvements

View file

@ -0,0 +1,66 @@
---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
title: Pebble SDK 2.1 - Changelog
date: 2014-05-06
---
## What has changed since SDK 2.0.2
Overview:
* Pebble dynamic memory allocation has been improved and will now detect when you try to free() memory twice.
* With Pebble 2.1 your application will be killed and a message is shown in the console so you can detect and fix this problem, instead of potentially causing a memory corruption issue.
* IMPORTANT: You will need to update your Pebble to run apps built with the 2.1 SDK. Applications compiled with the SDK 2.1 will not appear in the menu and will not run on Pebble firmware 2.0.
## Detailed List of Changes:
### Changes for Firmware:
* Fixed crash caused by calling number_window_set_label
* Fixed white line at the bottom of MenuLayer when last row is selected
* Fixed an issue where the watch would get into a reset loop after boot
* Fixed issue that sometimes caused persistent storage values to not persist
* Fixed issue where caller ID shows info from the previous call
* Fixed caller ID sometimes not displaying on outgoing calls
* Pebble dynamic memory allocation has been improved. Your application will now be killed when you try to free() memory twice
* Apps can no longer crash the watch on app exit
* Bluetooth reconnection is more reliable
* Battery monitor is more consistent
* Multiple power reduction improvements
* Documentation improvements
* Clip text instead of truncating when vertical space is inadequate
* Notifications can be cleared via the Notification section in the Settings menu
### Changes for PebbleKit iOS:
* Some improvements to datalogging to help troubleshoot issues
### Changes for PebbleKit Android:
* No changes
### Changes for SDK Tools:
* Allow firmware bundles to be installed with the install command
* Allow SDK location to be overridden by the `PEBBLE_SDK_PATH` environment variable
* Replaced PIL with pypng for taking screenshots
* Fixed extra row always being added to screenshots
### Changes for Examples:
* Removed ToDoList demo from SDK examples
### Changes for Documentation:
* Various documentation fixes and improvements

View file

@ -0,0 +1,50 @@
---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
title: Pebble SDK 2.2 - Changelog
date: 2014-06-04
---
## Detailed List of Changes:
### Changes for Firmware:
* Music app redesign to fix some layout issues & add progress bar
* Fix persist reads returning too little data if previously partially read
* Additional stability improvements
* Alarm now vibrates for 10 min instead of 1 min
* Launcher menu is now re-orderable. Hold the select button to enter reorder mode
* Volume control in the music app. Hold the select button in the music app to enter volume control mode
### Changes for PebbleKit iOS:
* Removed PBWatch+PhoneVersion (moved to PebblePrivateKit)
* Make PBWatch+Version report the correct version
* Fixed a crash when calling PBNumber description
### Changes for PebbleKit Android:
No changes
### Changes for SDK Tools:
No changes
### Changes for Examples:
No changes
### Changes for Documentation:
No changes

View file

@ -0,0 +1,68 @@
---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
title: Pebble SDK 2.3 - Changelog
date: 2014-06-30
---
## Detailed List of Changes:
### Changes for Firmware:
* Don't generate multiple single click events on release if a repeating click handler is also used
* Fixed a small memory leak when destroying number_layer objects
* Fixed a menu_layer display bug when header height is set to 0
* Allow app developers to supply their own ldscript
* Give a better error message when an unsupported libc function is used
* *_destroy functions now correctly do nothing when called with NULL pointers
* Fixed some BT LE connectivity issues
* Fixed a crash when we ran out of persist space
* Fixed a crash on reconnect when a user had a lot of pending iOS notifications
* Fixed an issue where the watch would continue to vibrate after a call is ended
* Fixed a display issue in Bluetooth settings when the status bar incorrectly says "Now Discoverable" in airplane mode
* Fixed a display issue with the notification font settings
* Fixed a display issue with the music app showing stale information when bluetooth is disconnected.
* Added the ability to skip to the next and previous notification by double clicking the up and down buttons
* Disabled the use of the back button for the Bluetooth pairing screen and the Alarm screen
* Show a status bar icon when notifications are set to "Phone Calls Only"
### Changes for PebbleKit iOS:
* Removed Bluetooth LE code from PebbleKit
* Improvements to data logging to help troubleshoot issues
* Removed PBWatch+PhoneVersion and +Polling
* Made PBWatch+Version report the correct version
* Fixed a crash when calling PBNumber description
* Changed imports from \<PebbleKit/HeaderName.h\> to "HeaderName.h" format
* Fixed on rare race-condition when sending data between phone and watch
* Made PebbleKit.podspec pass most-recent CocoaPod linter
* Prefixed internally used logging classes to fix conflict when using CocoaLumberjack in your app
* Made existing logging more descriptive
### Changes for PebbleKit Android:
No changes
### Changes for SDK Tools:
No changes
### Changes for Examples:
No changes
### Changes for Documentation:
* Added documentation for the calloc libc function
* Documented that text drawing functions use UTF-8 and will return errors on invalid input

View file

@ -0,0 +1,44 @@
---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
title: Pebble SDK 2.4.1 - Changelog
date: 2014-08-12
---
## Detailed List of Changes:
### Changes for Firmware:
* Fix a compilation problem that caused firmware 2.4 to reduce the amount of memory available to apps
### Changes for PebbleKit iOS:
No changes
### Changes for PebbleKit Android:
No changes
### Changes for SDK Tools:
No changes
### Changes for Examples:
No changes
### Changes for Documentation:
No changes

View file

@ -0,0 +1,53 @@
---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
title: Pebble SDK 2.4 - Changelog
date: 2014-08-11
---
## Detailed List of Changes:
### Changes for Firmware:
* Fix a potential crash when using scroll layers or animations
* Added support for realloc
* Added a gbitmap_create\_blank function to create empty bitmaps of a fixed size
* Added number_window\_get_window()
* Fixed a crash with atan2_lookup when high input values were used
* Fixed a bug where TupletInteger could not be used with unsigned integers
* Fixed several bluetooth reliability issues
* Fixed a case where the "Setup notifications" banner would erroneously show in the iOS Pebble app
* Fixed a bug with the music app where media playing faster than real time could not be paused
* Fixed a bug where the notifications view could show a rapidly increasing counter for number of notifications when first displayed
* Fixed a bug where switching watchfaces could cause the same watchface to be relaunched
### Changes for PebbleKit iOS:
No changes
### Changes for PebbleKit Android:
No changes
### Changes for SDK Tools:
No changes
### Changes for Examples:
No changes
### Changes for Documentation:
* Improved documentation around click handling

View file

@ -0,0 +1,84 @@
---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
title: Pebble SDK 2.5 - Changelog
date: 2014-09-18
---
>If you are upgrading from a previous version of the SDK, you will need to run the `pebble clean` command before using the SDK 2.5 with your project.
##Major Changes
* FW 2.5 includes an optimized version of ``snprintf`` (and related functions like ``APP_LOG``, etc) that does not support some length format specifiers previously supported (%hh, %ll, %j, %z, %t). The list of supported specifiers has been updated in the ``snprintf`` documentation. For those of you that use these previously-supported specifiers, please do not hesitate to [contact us](/contact) and we'd be happy to assist you with updating your code.
* Added [compass](/guides/events-and-services/compass) support.
* Enforced versionLabel formatting in appinfo.json in preparation for app auto updates.
* Added support for Pebble app relaunch on iOS when a Pebble watch is in proximity.
* Added notification dismissal support on iOS8.
* Added emoji support to Pebble notifications and system fonts.
## Detailed List of Changes:
### Changes for Firmware:
* Added functions ``heap_bytes_free`` and ``heap_bytes_used`` to view current heap memory usage.
* Added support for ``uuid_equal`` and ``uuid_to_string``.
* Added function ``accel_raw_data_service_subscribe`` to get accelerometer data with a single timestamp for all samples (significantly reduces memory usage for apps that do not depend on timestamps).
* Added [compass](/guides/events-and-services/compass) support.
* Added emoji support to Pebble notifications and system fonts `GOTHIC_24_BOLD`, `GOTHIC_18` and `GOTHIC_18_BOLD`.
* Fixed a bug that would cause a crash if a screen shot was taken while one was already in progress.
* Fixed an issue where Pebble APIs would use non-reentrant versions of standard C functions causing unexpected changes to return values.
* Fixed a bug with accel_service that could result in memory being freed twice.
* Fixed a bug where Golf API would show stale information on disconnect.
* Fixed a bug that prevented calling ``menu_layer_set_selected_index`` before ``menu_layer_set_callbacks``.
* Fixed a bug which would sometimes cause the command line logging tool to crash when a watchapp crashed.
* Fixed a bug that would cause the sample rate of the accelerometer to be reset when subscribing.
* Added support for Pebble app relaunch on iOS when a Pebble watch is in proximity.
* Added support for notification dismissal on iOS8.
* Fixed numerous bluetooth reliability & connection issues.
* Fixed a reset and other various bugs related to Data Logging.
* Fixed a bug that allowed backing out of FW update screen.
* Fixed a bug that would cause animations between windows to be slow.
* Fixed a bug where the Date UI would allow selection of invalid dates.
* Fixed a bug which would prevent the down button from scrolling through notification history.
* Fixed a bug with AVRCP that could lead to a crash.
* Set backlight to stay on during alarm ringing.
* Changed the default backlight setting to AUTO.
* Fixed a bug which would allow developers to ask for more than 25 accel samples per update.
* Added check for NULL parameter in ``gpath_draw_filled``.
### Changes for PebbleKit iOS:
PebbleKit iOS has been removed from the SDK download. Please find the latest PebbleKit iOS on [GitHub](https://github.com/pebble/pebble-ios-sdk) or on [CocoaPods](http://cocoapods.org/) under 'PebbleKit'.
### Changes for PebbleKit Android:
PebbleKit Android has been removed from the SDK download. Please find the latest PebbleKit Android on [GitHub](https://github.com/pebble/pebble-android-sdk).
### Changes for SDK Tools:
* Enforced versionLabel formatting in appinfo.json in preparation for app auto updates.
### Changes for Examples:
* Added [compass example]({{site.links.examples_org}}/feature-compass) application.
### Changes for Documentation:
* Added ``CompassService`` API document.
* Added missing ``calloc`` and ``realloc`` documentation.
* Improved ``tick_timer_service_subscribe`` documentation.
* Added missing ``RotBitmapLayer`` documentation.
* Corrected ``window_single_click_subscribe`` API entry.
* Corrected time_t time() function to specify that epoch adjusts for timezone and DST.
* Fixed typo in the ``AppMessage`` documentation.
* Improved ``gbitmap_create_with_data`` documentation.
* Fixed typo in documentation for ``resource_get_handle``.

View file

@ -0,0 +1,35 @@
---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
title: Pebble SDK 2.6.1 - Changelog
date: 2014-10-01
---
> This release is a hotfix for the SDK 2.6 release
### Changes for SDK Tools:
* Fix bug preventing use of `pebble analyze-size`
* Fix bug that caused compile errors with the use of custom fonts
---
### Pebble SDK 2.6 Release Summary ([full changelog](/sdk/changelogs/2.6/))
##### Major Changes:
* Add support for [background apps](/guides/events-and-services/background-worker) with ``AppWorker``
* Add ``graphics_capture_frame_buffer``, ``graphics_release_frame_buffer``, ``graphics_frame_buffer_is_captured`` APIs to expose framebuffer
* Add ``WatchInfo`` APIs to expose watch color, watch model, and firmware version
* Add quick launch support
* Bring back select-button-to-dismiss-notification on Android & iOS < 8
* Add --worker option to `pebble new-project` to create file structure for apps with background workers
* Add background worker [example]({{site.links.examples_org}}/feature-background-counter)

View file

@ -0,0 +1,47 @@
---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
title: Pebble SDK 2.6 - Changelog
date: 2014-09-30
---
> The symbols for `NUM_ANIMATION_CURVE` and `AnimationTimingFunction` have been removed in SDK 2.6. They were exposed in pebble.h in a previous release, but were not documented and are not used for ``Animation`` or ``PropertyAnimation`` APIs.
## Detailed List of Changes:
### Changes for Firmware:
* Add support for [background apps](/guides/events-and-services/background-worker/) with ``AppWorker``
> NOTE: The Background Worker API is not intended to be used as a wakeup mechanism for timer-based events or activities. SDK 2.7 will include a new Wakeup API that will allow you to set a timer that automatically launches your app in the foreground. Please do not use the Background Worker API to set such wakeups.
* Improve bluetooth connection service by only reporting disconnections of a certain length in time
* Add ``graphics_capture_frame_buffer``, ``graphics_release_frame_buffer``, ``graphics_frame_buffer_is_captured`` APIs to expose framebuffer
* Add ``WatchInfo`` APIs to expose watch color, watch model, and firmware version
* Fix bug where reading an existing key from persistent storage would fail
* Fixed Sports API bug causing menu item to not always appear in app launcher
* Fix bug with PebbleKit iOS and AppMessage timeouts
* Add quick launch support
* Bring back select-button-to-dismiss-notification on Android & iOS < 8
* Re-enable vibration when done charging
* Improve battery life
### Changes for SDK Tools:
* Add a --generate command line option to the coredump command
* Add --worker option to `pebble new-project` to create file structure for apps with background workers
### Changes for Examples:
* Add background worker [example]({{site.links.examples_org}}/feature-background-counter)
### Changes for Documentation:
* Add [AppWorker Guide](/guides/events-and-services/background-worker/)
* Add documentation for ``Worker``, ``AppWorker``, ``WatchInfo``

View file

@ -0,0 +1,39 @@
---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
title: Pebble SDK 2.7 - Changelog
date: 2014-10-16
---
## Detailed List of Changes:
### Changes for Firmware:
* Add ``Wakeup`` API
* Add ``launch_reason`` API
* Fix a bug that caused ``watch_info_get_color`` to crash the app when used
* Add ``clock_to_timestamp`` API
* Add ``clock_is_timezone_set`` API. In firmware 2.7, this function will always return false
as timezone support is not yet implemented
* Improve Bluetooth reliability
* Fix bug showing 0% battery warning
### Changes for SDK Tools:
No changes
### Changes for Examples:
* Add [wakeup example]({{site.links.examples_org}}/feature-app-wakeup)
### Changes for Documentation:
* Add ``Wakeup`` API documentation
* Fix bug with missing ``snprintf`` specifier documentation

View file

@ -0,0 +1,43 @@
---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
title: Pebble SDK 2.8.1 - Changelog
date: 2014-12-09
---
This release fixes a number of bugs and improves BLE pairing on iOS.
See [FW 2.8 Changelog](/sdk/changelogs/2.8/) for new features and major fixes.
## Detailed List of Changes:
### Changes for Firmware:
* Fix bug that would cause the watch to crash when ``accel_data_service_unsubscribe`` was called in a `window_unload` handler
* Revert error return values from ``resource_load_byte_range`` to pre-2.8 behavior
* Speed up BLE pairing on iOS
* Fix a bug that would cause an app to be built incorrectly if the first resource in appinfo.json was declared twice.
* Reduce stack usage of resource handling to prevent stack overflows introduced in 2.8
* Fix several strings in non-English languages
* Fix bug in ``AppMessage`` that would cause the watch to crash
### Changes for SDK Tools:
* Fix an issue where the SDK failed to build apps with non-ascii characters in the name.
* Include locale.h in pebble.h
### Changes for Examples:
No changes
### Changes for Documentation:
No changes

View file

@ -0,0 +1,57 @@
---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
title: Pebble SDK 2.8 - Changelog
date: 2014-11-20
---
This release changes the rendering behaviour of custom fonts in apps compiled with SDK 2.8. The change
improves the visual appearance of fonts, but also causes them to be slightly larger. If you rebuild with
SDK 2.8 and text no longer fits, you can revert to the old behaviour by setting `"compatibility": "2.7"`
in the resource block for that font, like so:
```javascript
{
"type": "font",
"file": "fonts/something.ttf",
"name": "FONT_SOMETHING_24",
"compatibility": "2.7"
}
```
System fonts are unaffected by this change.
## Detailed List of Changes:
### Changes for Firmware:
* All system `GOTHIC` fonts are expanded to contain 351 characters
* Add ``setlocale`` and ``i18n_get_system_locale`` APIs in preparation for internationalization support
* Fix an issue that could cause an incorrect accelerometer sampling rate to be used
* Fix an issue causing wakeup events scheduled less than thirty seconds in the future to fail
* Improve the performance of very small resource reads
* Fix an issue where iOS calendar alert notifications sometimes did not appear
* Fix an issue sometimes causing spurious "Loading..." notifications to appear on iOS
* Improve behaviour when trying to boot with a critically low battery
### Changes for SDK Tools:
* Improve font rendering for custom fonts when compiling with SDK 2.8
* This can change the font metrics. If the font no longer fits, add the flag `"compatibility": "2.7"`
to the resource entry for that font.
### Changes for Examples:
No changes
### Changes for Documentation:
* Fix explanation of the timezone of timestamps passed to ``wakeup_schedule``

View file

@ -0,0 +1,42 @@
---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
title: Pebble SDK 2.9 - Changelog
date: 2015-02-10
---
This release introduces actionable notifications for Android and minor stability improvements.
## Detailed List of Changes:
### Changes for Firmware:
* Add support for [Android actionable notifications](/blog/2014/12/19/Leverage-Android-Actionable-Notifications/)
* Fix bug that caused crashes when ``mktime()`` was used
* Fix behavior of ``window_stack_pop_all`` so that only the last window is animated
* Compiler will now show an error when the resources limit is reached
* Improve the stability of ``Worker``on launch
* Fix bug where a ``Worker`` selected from the Activity menus would not be set to default
* Fix bug where a ``Worker`` launched by a new app would not be set to default after the default worker was deleted
* Fix an issue that caused ``AppMessage`` to report sends as failed when sending/recieving a high volume of messages
* Notification date format is standardized: "Wednesday 11, February" -> "Wednesday, February 11"
### Changes for SDK Tools:
No changes
### Changes for Examples:
* Update openweather apis used in example apps.
### Changes for Documentation:
No changes

Some files were not shown because too many files have changed in this diff Show more